diff options
303 files changed, 6650 insertions, 2143 deletions
diff --git a/CREDITS.md b/CREDITS.md index 1b8ef7370..09719974d 100644 --- a/CREDITS.md +++ b/CREDITS.md | |||
@@ -1,4 +1,4 @@ | |||
1 | # Code | 1 | # Code contributors |
2 | 2 | ||
3 | * [Chocobozzz](https://github.com/Chocobozzz) | 3 | * [Chocobozzz](https://github.com/Chocobozzz) |
4 | * [rigelk](https://github.com/rigelk) | 4 | * [rigelk](https://github.com/rigelk) |
@@ -8,10 +8,12 @@ | |||
8 | * [Jorropo](https://github.com/Jorropo) | 8 | * [Jorropo](https://github.com/Jorropo) |
9 | * [buoyantair](https://github.com/buoyantair) | 9 | * [buoyantair](https://github.com/buoyantair) |
10 | * [bnjbvr](https://github.com/bnjbvr) | 10 | * [bnjbvr](https://github.com/bnjbvr) |
11 | * [frankstrater](https://github.com/frankstrater) | ||
11 | * [jankeromnes](https://github.com/jankeromnes) | 12 | * [jankeromnes](https://github.com/jankeromnes) |
12 | * [lucas-dclrcq](https://github.com/lucas-dclrcq) | 13 | * [lucas-dclrcq](https://github.com/lucas-dclrcq) |
13 | * [DavidLibeau](https://github.com/DavidLibeau) | ||
14 | * [JohnXLivingston](https://github.com/JohnXLivingston) | 14 | * [JohnXLivingston](https://github.com/JohnXLivingston) |
15 | * [DavidLibeau](https://github.com/DavidLibeau) | ||
16 | * [fflorent](https://github.com/fflorent) | ||
15 | * [kaiyou](https://github.com/kaiyou) | 17 | * [kaiyou](https://github.com/kaiyou) |
16 | * [ldidry](https://github.com/ldidry) | 18 | * [ldidry](https://github.com/ldidry) |
17 | * [McFlat](https://github.com/McFlat) | 19 | * [McFlat](https://github.com/McFlat) |
@@ -21,25 +23,25 @@ | |||
21 | * [NassimBounouas](https://github.com/NassimBounouas) | 23 | * [NassimBounouas](https://github.com/NassimBounouas) |
22 | * [thomaskuntzz](https://github.com/thomaskuntzz) | 24 | * [thomaskuntzz](https://github.com/thomaskuntzz) |
23 | * [rezonant](https://github.com/rezonant) | 25 | * [rezonant](https://github.com/rezonant) |
26 | * [Wirebrass](https://github.com/Wirebrass) | ||
24 | * [clementbrizard](https://github.com/clementbrizard) | 27 | * [clementbrizard](https://github.com/clementbrizard) |
25 | * [LecygneNoir](https://github.com/LecygneNoir) | 28 | * [LecygneNoir](https://github.com/LecygneNoir) |
26 | * [okhin](https://github.com/okhin) | 29 | * [okhin](https://github.com/okhin) |
27 | * [daftaupe](https://github.com/daftaupe) | 30 | * [daftaupe](https://github.com/daftaupe) |
28 | * [tcitworld](https://github.com/tcitworld) | 31 | * [tcitworld](https://github.com/tcitworld) |
29 | * [fflorent](https://github.com/fflorent) | ||
30 | * [dedesite](https://github.com/dedesite) | 32 | * [dedesite](https://github.com/dedesite) |
31 | * [Nautigsam](https://github.com/Nautigsam) | 33 | * [Nautigsam](https://github.com/Nautigsam) |
32 | * [scanlime](https://github.com/scanlime) | 34 | * [scanlime](https://github.com/scanlime) |
33 | * [am97](https://github.com/am97) | 35 | * [am97](https://github.com/am97) |
34 | * [dadall](https://github.com/dadall) | 36 | * [dadall](https://github.com/dadall) |
35 | * [jonathanraes](https://github.com/jonathanraes) | 37 | * [jonathanraes](https://github.com/jonathanraes) |
36 | * [Wirebrass](https://github.com/Wirebrass) | ||
37 | * [yohanboniface](https://github.com/yohanboniface) | 38 | * [yohanboniface](https://github.com/yohanboniface) |
38 | * [anoadragon453](https://github.com/anoadragon453) | 39 | * [anoadragon453](https://github.com/anoadragon453) |
39 | * [auberanger](https://github.com/auberanger) | 40 | * [auberanger](https://github.com/auberanger) |
40 | * [darnuria](https://github.com/darnuria) | 41 | * [darnuria](https://github.com/darnuria) |
41 | * [rhaamo](https://github.com/rhaamo) | 42 | * [rhaamo](https://github.com/rhaamo) |
42 | * [mrflos](https://github.com/mrflos) | 43 | * [mrflos](https://github.com/mrflos) |
44 | * [Yetangitu](https://github.com/Yetangitu) | ||
43 | * [jocelynj](https://github.com/jocelynj) | 45 | * [jocelynj](https://github.com/jocelynj) |
44 | * [lucaspontoexe](https://github.com/lucaspontoexe) | 46 | * [lucaspontoexe](https://github.com/lucaspontoexe) |
45 | * [flyingrub](https://github.com/flyingrub) | 47 | * [flyingrub](https://github.com/flyingrub) |
@@ -57,6 +59,7 @@ | |||
57 | * [Anton-Latukha](https://github.com/Anton-Latukha) | 59 | * [Anton-Latukha](https://github.com/Anton-Latukha) |
58 | * [noplanman](https://github.com/noplanman) | 60 | * [noplanman](https://github.com/noplanman) |
59 | * [austinheap](https://github.com/austinheap) | 61 | * [austinheap](https://github.com/austinheap) |
62 | * [BO41](https://github.com/BO41) | ||
60 | * [benabbottnz](https://github.com/benabbottnz) | 63 | * [benabbottnz](https://github.com/benabbottnz) |
61 | * [ewft](https://github.com/ewft) | 64 | * [ewft](https://github.com/ewft) |
62 | * [bradsk88](https://github.com/bradsk88) | 65 | * [bradsk88](https://github.com/bradsk88) |
@@ -67,7 +70,6 @@ | |||
67 | * [ebrehault](https://github.com/ebrehault) | 70 | * [ebrehault](https://github.com/ebrehault) |
68 | * [DatBewar](https://github.com/DatBewar) | 71 | * [DatBewar](https://github.com/DatBewar) |
69 | * [ReK2Fernandez](https://github.com/ReK2Fernandez) | 72 | * [ReK2Fernandez](https://github.com/ReK2Fernandez) |
70 | * [Yetangitu](https://github.com/Yetangitu) | ||
71 | * [grizio](https://github.com/grizio) | 73 | * [grizio](https://github.com/grizio) |
72 | * [Glandos](https://github.com/Glandos) | 74 | * [Glandos](https://github.com/Glandos) |
73 | * [lanodan](https://github.com/lanodan) | 75 | * [lanodan](https://github.com/lanodan) |
@@ -80,6 +82,7 @@ | |||
80 | * [pichouk](https://github.com/pichouk) | 82 | * [pichouk](https://github.com/pichouk) |
81 | * [LeoMouyna](https://github.com/LeoMouyna) | 83 | * [LeoMouyna](https://github.com/LeoMouyna) |
82 | * [LiPeK](https://github.com/LiPeK) | 84 | * [LiPeK](https://github.com/LiPeK) |
85 | * [Findus23](https://github.com/Findus23) | ||
83 | * [zapashcanon](https://github.com/zapashcanon) | 86 | * [zapashcanon](https://github.com/zapashcanon) |
84 | * [mart-e](https://github.com/mart-e) | 87 | * [mart-e](https://github.com/mart-e) |
85 | * [0mp](https://github.com/0mp) | 88 | * [0mp](https://github.com/0mp) |
@@ -95,6 +98,7 @@ | |||
95 | * [quentinDupont](https://github.com/quentinDupont) | 98 | * [quentinDupont](https://github.com/quentinDupont) |
96 | * [Quenty31](https://github.com/Quenty31) | 99 | * [Quenty31](https://github.com/Quenty31) |
97 | * [sundowndev](https://github.com/sundowndev) | 100 | * [sundowndev](https://github.com/sundowndev) |
101 | * [robinkooli](https://github.com/robinkooli) | ||
98 | * [sesn](https://github.com/sesn) | 102 | * [sesn](https://github.com/sesn) |
99 | * [ALSai](https://github.com/ALSai) | 103 | * [ALSai](https://github.com/ALSai) |
100 | * [Simounet](https://github.com/Simounet) | 104 | * [Simounet](https://github.com/Simounet) |
@@ -103,14 +107,13 @@ | |||
103 | * [FrozenDroid](https://github.com/FrozenDroid) | 107 | * [FrozenDroid](https://github.com/FrozenDroid) |
104 | * [fallen](https://github.com/fallen) | 108 | * [fallen](https://github.com/fallen) |
105 | * [melongbob](https://github.com/melongbob) | 109 | * [melongbob](https://github.com/melongbob) |
106 | * [Zig-03](https://github.com/Zig-03) | ||
107 | * [anmol26s](https://github.com/anmol26s) | 110 | * [anmol26s](https://github.com/anmol26s) |
108 | * [imbsky](https://github.com/imbsky) | 111 | * [imbsky](https://github.com/imbsky) |
109 | * [ctlaltdefeat](https://github.com/ctlaltdefeat) | 112 | * [ctlaltdefeat](https://github.com/ctlaltdefeat) |
110 | * [jomo](https://github.com/jomo) | 113 | * [jomo](https://github.com/jomo) |
111 | * [libertysoft3](https://github.com/libertysoft3) | 114 | * [libertysoft3](https://github.com/libertysoft3) |
112 | * [lsde](https://github.com/lsde) | 115 | * [lsde](https://github.com/lsde) |
113 | * [memoryboxes](https://github.com/memoryboxes) | 116 | * [brain-zhang](https://github.com/brain-zhang) |
114 | * [norrist](https://github.com/norrist) | 117 | * [norrist](https://github.com/norrist) |
115 | * [osauzet](https://github.com/osauzet) | 118 | * [osauzet](https://github.com/osauzet) |
116 | * [SansPseudoFix](https://github.com/SansPseudoFix) | 119 | * [SansPseudoFix](https://github.com/SansPseudoFix) |
@@ -121,7 +124,7 @@ | |||
121 | * [ewasion](https://github.com/ewasion) | 124 | * [ewasion](https://github.com/ewasion) |
122 | 125 | ||
123 | 126 | ||
124 | # Translations | 127 | # Translation contributors |
125 | 128 | ||
126 | * [abdhessuk](https://trad.framasoft.org/zanata/profile/view/abdhessuk) | 129 | * [abdhessuk](https://trad.framasoft.org/zanata/profile/view/abdhessuk) |
127 | * [abidin24](https://trad.framasoft.org/zanata/profile/view/abidin24) | 130 | * [abidin24](https://trad.framasoft.org/zanata/profile/view/abidin24) |
@@ -196,6 +199,7 @@ | |||
196 | * [abidin24](https://trad.framasoft.org/zanata/profile/view/abidin24) | 199 | * [abidin24](https://trad.framasoft.org/zanata/profile/view/abidin24) |
197 | * [aditoo](https://trad.framasoft.org/zanata/profile/view/aditoo) | 200 | * [aditoo](https://trad.framasoft.org/zanata/profile/view/aditoo) |
198 | * [alidemirtas](https://trad.framasoft.org/zanata/profile/view/alidemirtas) | 201 | * [alidemirtas](https://trad.framasoft.org/zanata/profile/view/alidemirtas) |
202 | * [anastasia](https://trad.framasoft.org/zanata/profile/view/anastasia) | ||
199 | * [ariasuni](https://trad.framasoft.org/zanata/profile/view/ariasuni) | 203 | * [ariasuni](https://trad.framasoft.org/zanata/profile/view/ariasuni) |
200 | * [autom](https://trad.framasoft.org/zanata/profile/view/autom) | 204 | * [autom](https://trad.framasoft.org/zanata/profile/view/autom) |
201 | * [balaji](https://trad.framasoft.org/zanata/profile/view/balaji) | 205 | * [balaji](https://trad.framasoft.org/zanata/profile/view/balaji) |
@@ -203,13 +207,16 @@ | |||
203 | * [bristow](https://trad.framasoft.org/zanata/profile/view/bristow) | 207 | * [bristow](https://trad.framasoft.org/zanata/profile/view/bristow) |
204 | * [butterflyoffire](https://trad.framasoft.org/zanata/profile/view/butterflyoffire) | 208 | * [butterflyoffire](https://trad.framasoft.org/zanata/profile/view/butterflyoffire) |
205 | * [c0dr](https://trad.framasoft.org/zanata/profile/view/c0dr) | 209 | * [c0dr](https://trad.framasoft.org/zanata/profile/view/c0dr) |
210 | * [canony](https://trad.framasoft.org/zanata/profile/view/canony) | ||
206 | * [cat](https://trad.framasoft.org/zanata/profile/view/cat) | 211 | * [cat](https://trad.framasoft.org/zanata/profile/view/cat) |
207 | * [chocobozzz](https://trad.framasoft.org/zanata/profile/view/chocobozzz) | 212 | * [chocobozzz](https://trad.framasoft.org/zanata/profile/view/chocobozzz) |
208 | * [clerie](https://trad.framasoft.org/zanata/profile/view/clerie) | 213 | * [clerie](https://trad.framasoft.org/zanata/profile/view/clerie) |
209 | * [curupira](https://trad.framasoft.org/zanata/profile/view/curupira) | 214 | * [curupira](https://trad.framasoft.org/zanata/profile/view/curupira) |
210 | * [dhsets](https://trad.framasoft.org/zanata/profile/view/dhsets) | 215 | * [dhsets](https://trad.framasoft.org/zanata/profile/view/dhsets) |
216 | * [dibek](https://trad.framasoft.org/zanata/profile/view/dibek) | ||
211 | * [digitalkiller](https://trad.framasoft.org/zanata/profile/view/digitalkiller) | 217 | * [digitalkiller](https://trad.framasoft.org/zanata/profile/view/digitalkiller) |
212 | * [dwsage](https://trad.framasoft.org/zanata/profile/view/dwsage) | 218 | * [dwsage](https://trad.framasoft.org/zanata/profile/view/dwsage) |
219 | * [fkohrt](https://trad.framasoft.org/zanata/profile/view/fkohrt) | ||
213 | * [flauta](https://trad.framasoft.org/zanata/profile/view/flauta) | 220 | * [flauta](https://trad.framasoft.org/zanata/profile/view/flauta) |
214 | * [frankstrater](https://trad.framasoft.org/zanata/profile/view/frankstrater) | 221 | * [frankstrater](https://trad.framasoft.org/zanata/profile/view/frankstrater) |
215 | * [gillux](https://trad.framasoft.org/zanata/profile/view/gillux) | 222 | * [gillux](https://trad.framasoft.org/zanata/profile/view/gillux) |
@@ -220,17 +227,25 @@ | |||
220 | * [jhertel](https://trad.framasoft.org/zanata/profile/view/jhertel) | 227 | * [jhertel](https://trad.framasoft.org/zanata/profile/view/jhertel) |
221 | * [joss2lyon](https://trad.framasoft.org/zanata/profile/view/joss2lyon) | 228 | * [joss2lyon](https://trad.framasoft.org/zanata/profile/view/joss2lyon) |
222 | * [kekkotranslates](https://trad.framasoft.org/zanata/profile/view/kekkotranslates) | 229 | * [kekkotranslates](https://trad.framasoft.org/zanata/profile/view/kekkotranslates) |
230 | * [kingu](https://trad.framasoft.org/zanata/profile/view/kingu) | ||
223 | * [kittybecca](https://trad.framasoft.org/zanata/profile/view/kittybecca) | 231 | * [kittybecca](https://trad.framasoft.org/zanata/profile/view/kittybecca) |
232 | * [kousha](https://trad.framasoft.org/zanata/profile/view/kousha) | ||
224 | * [krkk](https://trad.framasoft.org/zanata/profile/view/krkk) | 233 | * [krkk](https://trad.framasoft.org/zanata/profile/view/krkk) |
234 | * [lapor](https://trad.framasoft.org/zanata/profile/view/lapor) | ||
225 | * [laufor](https://trad.framasoft.org/zanata/profile/view/laufor) | 235 | * [laufor](https://trad.framasoft.org/zanata/profile/view/laufor) |
226 | * [leeroyepold48](https://trad.framasoft.org/zanata/profile/view/leeroyepold48) | 236 | * [leeroyepold48](https://trad.framasoft.org/zanata/profile/view/leeroyepold48) |
227 | * [lstamellos](https://trad.framasoft.org/zanata/profile/view/lstamellos) | 237 | * [lstamellos](https://trad.framasoft.org/zanata/profile/view/lstamellos) |
228 | * [mablr](https://trad.framasoft.org/zanata/profile/view/mablr) | 238 | * [mablr](https://trad.framasoft.org/zanata/profile/view/mablr) |
229 | * [marcinmalecki](https://trad.framasoft.org/zanata/profile/view/marcinmalecki) | 239 | * [marcinmalecki](https://trad.framasoft.org/zanata/profile/view/marcinmalecki) |
230 | * [matograine](https://trad.framasoft.org/zanata/profile/view/matograine) | 240 | * [matograine](https://trad.framasoft.org/zanata/profile/view/matograine) |
241 | * [mayana](https://trad.framasoft.org/zanata/profile/view/mayana) | ||
231 | * [mikeorlov](https://trad.framasoft.org/zanata/profile/view/mikeorlov) | 242 | * [mikeorlov](https://trad.framasoft.org/zanata/profile/view/mikeorlov) |
232 | * [nin](https://trad.framasoft.org/zanata/profile/view/nin) | 243 | * [nin](https://trad.framasoft.org/zanata/profile/view/nin) |
244 | * [noncommutativegeo](https://trad.framasoft.org/zanata/profile/view/noncommutativegeo) | ||
233 | * [norbipeti](https://trad.framasoft.org/zanata/profile/view/norbipeti) | 245 | * [norbipeti](https://trad.framasoft.org/zanata/profile/view/norbipeti) |
246 | * [nvivant](https://trad.framasoft.org/zanata/profile/view/nvivant) | ||
247 | * [osoitz](https://trad.framasoft.org/zanata/profile/view/osoitz) | ||
248 | * [ppnplus](https://trad.framasoft.org/zanata/profile/view/ppnplus) | ||
234 | * [predatorix](https://trad.framasoft.org/zanata/profile/view/predatorix) | 249 | * [predatorix](https://trad.framasoft.org/zanata/profile/view/predatorix) |
235 | * [quentin](https://trad.framasoft.org/zanata/profile/view/quentin) | 250 | * [quentin](https://trad.framasoft.org/zanata/profile/view/quentin) |
236 | * [quentind](https://trad.framasoft.org/zanata/profile/view/quentind) | 251 | * [quentind](https://trad.framasoft.org/zanata/profile/view/quentind) |
@@ -238,11 +253,14 @@ | |||
238 | * [robin](https://trad.framasoft.org/zanata/profile/view/robin) | 253 | * [robin](https://trad.framasoft.org/zanata/profile/view/robin) |
239 | * [rond](https://trad.framasoft.org/zanata/profile/view/rond) | 254 | * [rond](https://trad.framasoft.org/zanata/profile/view/rond) |
240 | * [s8321414](https://trad.framasoft.org/zanata/profile/view/s8321414) | 255 | * [s8321414](https://trad.framasoft.org/zanata/profile/view/s8321414) |
256 | * [sato_ss](https://trad.framasoft.org/zanata/profile/view/sato_ss) | ||
241 | * [secreet](https://trad.framasoft.org/zanata/profile/view/secreet) | 257 | * [secreet](https://trad.framasoft.org/zanata/profile/view/secreet) |
258 | * [sercom_kc](https://trad.framasoft.org/zanata/profile/view/sercom_kc) | ||
242 | * [severo](https://trad.framasoft.org/zanata/profile/view/severo) | 259 | * [severo](https://trad.framasoft.org/zanata/profile/view/severo) |
243 | * [silkevicious](https://trad.framasoft.org/zanata/profile/view/silkevicious) | 260 | * [silkevicious](https://trad.framasoft.org/zanata/profile/view/silkevicious) |
244 | * [sporiff](https://trad.framasoft.org/zanata/profile/view/sporiff) | 261 | * [sporiff](https://trad.framasoft.org/zanata/profile/view/sporiff) |
245 | * [tekuteku](https://trad.framasoft.org/zanata/profile/view/tekuteku) | 262 | * [tekuteku](https://trad.framasoft.org/zanata/profile/view/tekuteku) |
263 | * [thecatjustmeow](https://trad.framasoft.org/zanata/profile/view/thecatjustmeow) | ||
246 | * [tirifto](https://trad.framasoft.org/zanata/profile/view/tirifto) | 264 | * [tirifto](https://trad.framasoft.org/zanata/profile/view/tirifto) |
247 | * [tmota](https://trad.framasoft.org/zanata/profile/view/tmota) | 265 | * [tmota](https://trad.framasoft.org/zanata/profile/view/tmota) |
248 | * [tuxayo](https://trad.framasoft.org/zanata/profile/view/tuxayo) | 266 | * [tuxayo](https://trad.framasoft.org/zanata/profile/view/tuxayo) |
@@ -22,7 +22,7 @@ Be part of a network of multiple small federated, interoperable video hosting pr | |||
22 | 22 | ||
23 | <p align="center"> | 23 | <p align="center"> |
24 | <a href="https://framasoft.org"> | 24 | <a href="https://framasoft.org"> |
25 | <img width="150px" src="http://lutim.cpy.re/Prd3ci7G.png" alt="Framasoft logo"/> | 25 | <img width="150px" src="https://lutim.cpy.re/FeRgHH8r.png" alt="Framasoft logo"/> |
26 | </a> | 26 | </a> |
27 | </p> | 27 | </p> |
28 | 28 | ||
@@ -182,7 +182,7 @@ See the [architecture blueprint](https://docs.joinpeertube.org/#/contribute-arch | |||
182 | 182 | ||
183 | See our REST API documentation: | 183 | See our REST API documentation: |
184 | * OpenAPI 3.0.0 schema: [/support/doc/api/openapi.yaml](/support/doc/api/openapi.yaml) | 184 | * OpenAPI 3.0.0 schema: [/support/doc/api/openapi.yaml](/support/doc/api/openapi.yaml) |
185 | * Spec explorer: [docs.joinpeertube.org/#/api-rest-reference.html](https://docs.joinpeertube.org/#/api-rest-reference.html) | 185 | * Spec explorer: [docs.joinpeertube.org/api-rest-reference.html](https://docs.joinpeertube.org/api-rest-reference.html) |
186 | 186 | ||
187 | See our [ActivityPub documentation](https://docs.joinpeertube.org/#/api-activitypub). | 187 | See our [ActivityPub documentation](https://docs.joinpeertube.org/#/api-activitypub). |
188 | 188 | ||
diff --git a/client/package.json b/client/package.json index ba4f5000d..39fd63434 100644 --- a/client/package.json +++ b/client/package.json | |||
@@ -100,7 +100,7 @@ | |||
100 | "ngx-pipes": "^2.1.7", | 100 | "ngx-pipes": "^2.1.7", |
101 | "node-sass": "^4.9.3", | 101 | "node-sass": "^4.9.3", |
102 | "npm-font-source-sans-pro": "^1.0.2", | 102 | "npm-font-source-sans-pro": "^1.0.2", |
103 | "p2p-media-loader-hlsjs": "^0.6.1", | 103 | "p2p-media-loader-hlsjs": "^0.6.2", |
104 | "path-browserify": "^1.0.0", | 104 | "path-browserify": "^1.0.0", |
105 | "primeng": "^8.0.2", | 105 | "primeng": "^8.0.2", |
106 | "process": "^0.11.10", | 106 | "process": "^0.11.10", |
diff --git a/client/src/app/+about/about-instance/about-instance.component.html b/client/src/app/+about/about-instance/about-instance.component.html index 7c27ec760..25d416740 100644 --- a/client/src/app/+about/about-instance/about-instance.component.html +++ b/client/src/app/+about/about-instance/about-instance.component.html | |||
@@ -1,32 +1,97 @@ | |||
1 | <div class="row"> | 1 | <div class="row"> |
2 | <div class="col-md-12 col-xl-6"> | 2 | <div class="col-md-12 col-xl-6"> |
3 | |||
3 | <div class="about-instance-title"> | 4 | <div class="about-instance-title"> |
4 | <div i18n>About {{ instanceName }} instance</div> | 5 | <div i18n class="title">About {{ instanceName }} instance</div> |
5 | 6 | ||
6 | <div *ngIf="isContactFormEnabled" (click)="openContactModal()" i18n role="button" class="contact-admin">Contact administrator</div> | 7 | <div i18n *ngIf="isContactFormEnabled" (click)="openContactModal()" role="button" class="contact-admin">Contact administrator</div> |
8 | </div> | ||
9 | |||
10 | <div class="block instance-badges"> | ||
11 | <span *ngFor="let category of categories" class="badge badge-primary category">{{ category }}</span> | ||
12 | |||
13 | <span *ngFor="let language of languages" class="badge badge-secondary language">{{ language }}</span> | ||
7 | </div> | 14 | </div> |
8 | 15 | ||
9 | <div class="short-description"> | 16 | <div class="short-description"> |
10 | <div>{{ shortDescription }}</div> | 17 | <div class="block short-description">{{ shortDescription }}</div> |
18 | |||
19 | <div i18n *ngIf="isNSFW" class="block dedicated-to-nsfw">This instance is dedicated to sensitive/NSFW content.</div> | ||
20 | </div> | ||
21 | |||
22 | <div i18n class="middle-title" *ngIf="html.administrator || maintenanceLifetime || businessModel"> | ||
23 | Administrators & sustainability | ||
24 | </div> | ||
25 | |||
26 | <div class="block administrator" *ngIf="html.administrator"> | ||
27 | <div i18n class="section-title">Who we are</div> | ||
11 | 28 | ||
12 | <div *ngIf="isNSFW" class="dedicated-to-nsfw">This instance is dedicated to sensitive/NSFW content.</div> | 29 | <div [innerHTML]="html.administrator"></div> |
13 | </div> | 30 | </div> |
14 | 31 | ||
15 | <div class="description"> | 32 | <div class="block creation-reason" *ngIf="creationReason"> |
33 | <div i18n class="section-title">Why we created this instance</div> | ||
34 | |||
35 | <p>{{ creationReason }}</p> | ||
36 | </div> | ||
37 | |||
38 | <div class="block maintenance-lifetime" *ngIf="maintenanceLifetime"> | ||
39 | <div i18n class="section-title">How long we plan to maintain this instance</div> | ||
40 | |||
41 | <p>{{ maintenanceLifetime }}</p> | ||
42 | </div> | ||
43 | |||
44 | <div class="block business-model" *ngIf="businessModel"> | ||
45 | <div i18n class="section-title">How we will pay this instance</div> | ||
46 | |||
47 | <p>{{ businessModel }}</p> | ||
48 | </div> | ||
49 | |||
50 | <div i18n class="middle-title" *ngIf="html.description"> | ||
51 | Information | ||
52 | </div> | ||
53 | |||
54 | <div class="block description"> | ||
16 | <div i18n class="section-title">Description</div> | 55 | <div i18n class="section-title">Description</div> |
17 | 56 | ||
18 | <div [innerHTML]="descriptionHTML"></div> | 57 | <div [innerHTML]="html.description"></div> |
58 | </div> | ||
59 | |||
60 | <div i18n class="middle-title" *ngIf="html.moderationInformation || html.codeOfConduct || html.terms"> | ||
61 | Moderation | ||
62 | </div> | ||
63 | |||
64 | <div class="block moderation-information" *ngIf="html.moderationInformation"> | ||
65 | <div i18n class="section-title">Moderation information</div> | ||
66 | |||
67 | <div [innerHTML]="html.moderationInformation"></div> | ||
19 | </div> | 68 | </div> |
20 | 69 | ||
21 | <div class="terms" id="terms-section"> | 70 | <div class="block code-of-conduct" *ngIf="html.codeOfConduct"> |
71 | <div i18n class="section-title">Code of conduct</div> | ||
72 | |||
73 | <div [innerHTML]="html.codeOfConduct"></div> | ||
74 | </div> | ||
75 | |||
76 | <div class="block terms"> | ||
22 | <div i18n class="section-title">Terms</div> | 77 | <div i18n class="section-title">Terms</div> |
23 | 78 | ||
24 | <div [innerHTML]="termsHTML"></div> | 79 | <div [innerHTML]="html.terms"></div> |
80 | </div> | ||
81 | |||
82 | <div i18n class="middle-title" *ngIf="html.hardwareInformation"> | ||
83 | Other information | ||
84 | </div> | ||
85 | |||
86 | <div class="block hardware-information"> | ||
87 | <div i18n class="section-title">Hardware information</div> | ||
88 | |||
89 | <div [innerHTML]="html.hardwareInformation"></div> | ||
25 | </div> | 90 | </div> |
26 | </div> | 91 | </div> |
27 | 92 | ||
28 | <div class="col-md-12 col-xl-6"> | 93 | <div class="col-md-12 col-xl-6"> |
29 | <label>Features found on this instance</label> | 94 | <label i18n>Features found on this instance</label> |
30 | <my-instance-features-table></my-instance-features-table> | 95 | <my-instance-features-table></my-instance-features-table> |
31 | </div> | 96 | </div> |
32 | </div> | 97 | </div> |
diff --git a/client/src/app/+about/about-instance/about-instance.component.scss b/client/src/app/+about/about-instance/about-instance.component.scss index 0296ae8e9..909ae5c21 100644 --- a/client/src/app/+about/about-instance/about-instance.component.scss +++ b/client/src/app/+about/about-instance/about-instance.component.scss | |||
@@ -5,13 +5,12 @@ | |||
5 | display: flex; | 5 | display: flex; |
6 | justify-content: space-between; | 6 | justify-content: space-between; |
7 | 7 | ||
8 | & > div { | 8 | .title { |
9 | font-size: 20px; | 9 | font-size: 20px; |
10 | font-weight: bold; | 10 | font-weight: $font-semibold; |
11 | margin-bottom: 15px; | ||
12 | } | 11 | } |
13 | 12 | ||
14 | & > .contact-admin { | 13 | .contact-admin { |
15 | @include peertube-button; | 14 | @include peertube-button; |
16 | @include orange-button; | 15 | @include orange-button; |
17 | 16 | ||
@@ -19,14 +18,38 @@ | |||
19 | } | 18 | } |
20 | } | 19 | } |
21 | 20 | ||
21 | .instance-badges { | ||
22 | font-size: 16px; | ||
23 | |||
24 | .badge { | ||
25 | font-size: 12px; | ||
26 | font-weight: $font-semibold; | ||
27 | margin-right: 5px; | ||
28 | |||
29 | &.category { | ||
30 | background-color: var(--mainColor); | ||
31 | } | ||
32 | } | ||
33 | } | ||
34 | |||
22 | .section-title { | 35 | .section-title { |
23 | font-weight: $font-semibold; | 36 | font-weight: $font-semibold; |
24 | font-size: 20px; | 37 | font-size: 16px; |
25 | margin-bottom: 5px; | 38 | margin-bottom: 5px; |
39 | display: flex; | ||
40 | align-items: center; | ||
41 | } | ||
42 | |||
43 | .middle-title { | ||
44 | @include in-content-small-title; | ||
45 | |||
46 | margin-top: 45px; | ||
47 | margin-bottom: 25px; | ||
26 | } | 48 | } |
27 | 49 | ||
28 | .short-description, .description, .terms, .signup { | 50 | .block { |
29 | margin-bottom: 30px; | 51 | margin-bottom: 30px; |
52 | font-size: 15px; | ||
30 | } | 53 | } |
31 | 54 | ||
32 | .short-description .dedicated-to-nsfw { | 55 | .short-description .dedicated-to-nsfw { |
diff --git a/client/src/app/+about/about-instance/about-instance.component.ts b/client/src/app/+about/about-instance/about-instance.component.ts index a5204de27..16ccae2e2 100644 --- a/client/src/app/+about/about-instance/about-instance.component.ts +++ b/client/src/app/+about/about-instance/about-instance.component.ts | |||
@@ -4,6 +4,8 @@ import { I18n } from '@ngx-translate/i18n-polyfill' | |||
4 | import { ContactAdminModalComponent } from '@app/+about/about-instance/contact-admin-modal.component' | 4 | import { ContactAdminModalComponent } from '@app/+about/about-instance/contact-admin-modal.component' |
5 | import { InstanceService } from '@app/shared/instance/instance.service' | 5 | import { InstanceService } from '@app/shared/instance/instance.service' |
6 | import { MarkdownService } from '@app/shared/renderer' | 6 | import { MarkdownService } from '@app/shared/renderer' |
7 | import { forkJoin } from 'rxjs' | ||
8 | import { first } from 'rxjs/operators' | ||
7 | 9 | ||
8 | @Component({ | 10 | @Component({ |
9 | selector: 'my-about-instance', | 11 | selector: 'my-about-instance', |
@@ -14,8 +16,22 @@ export class AboutInstanceComponent implements OnInit { | |||
14 | @ViewChild('contactAdminModal', { static: true }) contactAdminModal: ContactAdminModalComponent | 16 | @ViewChild('contactAdminModal', { static: true }) contactAdminModal: ContactAdminModalComponent |
15 | 17 | ||
16 | shortDescription = '' | 18 | shortDescription = '' |
17 | descriptionHTML = '' | 19 | |
18 | termsHTML = '' | 20 | html = { |
21 | description: '', | ||
22 | terms: '', | ||
23 | codeOfConduct: '', | ||
24 | moderationInformation: '', | ||
25 | administrator: '', | ||
26 | hardwareInformation: '' | ||
27 | } | ||
28 | |||
29 | creationReason = '' | ||
30 | maintenanceLifetime = '' | ||
31 | businessModel = '' | ||
32 | |||
33 | languages: string[] = [] | ||
34 | categories: string[] = [] | ||
19 | 35 | ||
20 | constructor ( | 36 | constructor ( |
21 | private notifier: Notifier, | 37 | private notifier: Notifier, |
@@ -38,21 +54,30 @@ export class AboutInstanceComponent implements OnInit { | |||
38 | } | 54 | } |
39 | 55 | ||
40 | ngOnInit () { | 56 | ngOnInit () { |
41 | this.instanceService.getAbout() | 57 | forkJoin([ |
42 | .subscribe( | 58 | this.instanceService.getAbout(), |
43 | async res => { | 59 | this.serverService.localeObservable.pipe(first()), |
44 | this.shortDescription = res.instance.shortDescription | 60 | this.serverService.videoLanguagesLoaded.pipe(first()), |
61 | this.serverService.videoCategoriesLoaded.pipe(first()) | ||
62 | ]).subscribe( | ||
63 | async ([ about, translations ]) => { | ||
64 | this.shortDescription = about.instance.shortDescription | ||
45 | 65 | ||
46 | this.descriptionHTML = await this.markdownService.textMarkdownToHTML(res.instance.description) | 66 | this.creationReason = about.instance.creationReason |
47 | this.termsHTML = await this.markdownService.textMarkdownToHTML(res.instance.terms) | 67 | this.maintenanceLifetime = about.instance.maintenanceLifetime |
48 | }, | 68 | this.businessModel = about.instance.businessModel |
49 | 69 | ||
50 | () => this.notifier.error(this.i18n('Cannot get about information from server')) | 70 | this.html = await this.instanceService.buildHtml(about) |
51 | ) | 71 | |
72 | this.languages = this.instanceService.buildTranslatedLanguages(about, translations) | ||
73 | this.categories = this.instanceService.buildTranslatedCategories(about, translations) | ||
74 | }, | ||
75 | |||
76 | () => this.notifier.error(this.i18n('Cannot get about information from server')) | ||
77 | ) | ||
52 | } | 78 | } |
53 | 79 | ||
54 | openContactModal () { | 80 | openContactModal () { |
55 | return this.contactAdminModal.show() | 81 | return this.contactAdminModal.show() |
56 | } | 82 | } |
57 | |||
58 | } | 83 | } |
diff --git a/client/src/app/+about/about-peertube/about-peertube-contributors.component.html b/client/src/app/+about/about-peertube/about-peertube-contributors.component.html new file mode 100644 index 000000000..997a6a3e1 --- /dev/null +++ b/client/src/app/+about/about-peertube/about-peertube-contributors.component.html | |||
@@ -0,0 +1,13 @@ | |||
1 | <h3 i18n class="section-title">Who made this software?</h3> | ||
2 | |||
3 | <p align="center"> | ||
4 | <strong>Developed with ❤ by <a target="_blank" rel="noopener noreferrer" href="https://framasoft.org">Framasoft</a></strong> | ||
5 | </p> | ||
6 | |||
7 | <p align="center"> | ||
8 | <a target="_blank" rel="noopener noreferrer" href="https://framasoft.org"> | ||
9 | <img width="150px" src="/client/assets/images/framasoft.png" alt="Framasoft logo"/> | ||
10 | </a> | ||
11 | </p> | ||
12 | |||
13 | <div [innerHTML]="creditsHtml"></div> | ||
diff --git a/client/src/app/+about/about-peertube/about-peertube-contributors.component.scss b/client/src/app/+about/about-peertube/about-peertube-contributors.component.scss new file mode 100644 index 000000000..9c3b0a46b --- /dev/null +++ b/client/src/app/+about/about-peertube/about-peertube-contributors.component.scss | |||
@@ -0,0 +1,15 @@ | |||
1 | @import '_variables'; | ||
2 | @import '_mixins'; | ||
3 | |||
4 | /deep/ h1 { | ||
5 | font-size: 1rem; | ||
6 | } | ||
7 | |||
8 | /deep/ ul { | ||
9 | padding: 0; | ||
10 | |||
11 | li { | ||
12 | display: inline-block; | ||
13 | margin-right: 10px; | ||
14 | } | ||
15 | } | ||
diff --git a/client/src/app/+about/about-peertube/about-peertube-contributors.component.ts b/client/src/app/+about/about-peertube/about-peertube-contributors.component.ts new file mode 100644 index 000000000..fa2c0daa0 --- /dev/null +++ b/client/src/app/+about/about-peertube/about-peertube-contributors.component.ts | |||
@@ -0,0 +1,19 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | ||
2 | import { MarkdownService } from '@app/shared/renderer' | ||
3 | |||
4 | @Component({ | ||
5 | selector: 'my-about-peertube-contributors', | ||
6 | templateUrl: './about-peertube-contributors.component.html', | ||
7 | styleUrls: [ './about-peertube-contributors.component.scss' ] | ||
8 | }) | ||
9 | export class AboutPeertubeContributorsComponent implements OnInit { | ||
10 | creditsHtml: string | ||
11 | |||
12 | private markdown = require('raw-loader!../../../../../CREDITS.md') | ||
13 | |||
14 | constructor (private markdownService: MarkdownService) { } | ||
15 | |||
16 | async ngOnInit () { | ||
17 | this.creditsHtml = await this.markdownService.completeMarkdownToHTML(this.markdown) | ||
18 | } | ||
19 | } | ||
diff --git a/client/src/app/+about/about-peertube/about-peertube.component.html b/client/src/app/+about/about-peertube/about-peertube.component.html index d3fc9a828..423f7bce7 100644 --- a/client/src/app/+about/about-peertube/about-peertube.component.html +++ b/client/src/app/+about/about-peertube/about-peertube.component.html | |||
@@ -14,84 +14,89 @@ | |||
14 | </p> | 14 | </p> |
15 | </div> | 15 | </div> |
16 | 16 | ||
17 | <div id="p2p-privacy"> | 17 | <div class="privacy-contributors"> |
18 | <h3 i18n class="section-title">P2P & Privacy</h3> | 18 | <my-about-peertube-contributors></my-about-peertube-contributors> |
19 | |||
20 | <div class="p2p-privacy"> | ||
21 | <h3 i18n class="section-title">P2P & Privacy</h3> | ||
22 | |||
23 | <p i18n> | ||
24 | PeerTube uses the BitTorrent protocol to share bandwidth between users. | ||
25 | This implies that your IP address is stored in the instance's BitTorrent tracker as long as you download or watch the video. | ||
26 | </p> | ||
27 | |||
28 | <h6 i18n class="p2p-privacy-title">What are the consequences?</h6> | ||
29 | |||
30 | <p i18n> | ||
31 | In theory, someone with enough technical skills could create a script that tracks which IP is downloading which video. | ||
32 | In practice, this is much more difficult because: | ||
33 | </p> | ||
34 | |||
35 | <ul> | ||
36 | <li i18n> | ||
37 | An HTTP request has to be sent on each tracker for each video to spy. | ||
38 | If we want to spy all PeerTube's videos, we have to send as many requests as there are videos (so potentially a lot) | ||
39 | </li> | ||
40 | |||
41 | <li i18n> | ||
42 | For each request sent, the tracker returns random peers at a limited number. | ||
43 | For instance, if there are 1000 peers in the swarm and the tracker sends only 20 peers for each request, there must be at least 50 requests sent to know every peers in the swarm | ||
44 | </li> | ||
45 | |||
46 | <li i18n> | ||
47 | Those requests have to be sent regularly to know who starts/stops watching a video. It is easy to detect that kind of behaviour | ||
48 | </li> | ||
49 | |||
50 | <li i18n> | ||
51 | If an IP address is stored in the tracker, it doesn't mean that the person behind the IP (if this person exists) has watched the video | ||
52 | </li> | ||
53 | |||
54 | <li i18n> | ||
55 | The IP address is a vague information : usually, it regularly changes and can represent many persons or entities | ||
56 | </li> | ||
57 | |||
58 | <li i18n> | ||
59 | Web peers are not publicly accessible: because we use WebRTC inside the web browser (<a href="https://webtorrent.io/">with the WebTorrent library</a>), the protocol is different from classic BitTorrent. | ||
60 | When you are in a web browser, you send a signal containing your IP address to the tracker that will randomly choose other peers to forward the information to. | ||
61 | See <a href="https://github.com/yciabaud/webtorrent/blob/beps/bep_webrtc.rst">this document</a> for more information | ||
62 | </li> | ||
63 | </ul> | ||
64 | |||
65 | <p i18n> | ||
66 | The worst-case scenario of an average person spying on their friends is quite unlikely. | ||
67 | There are much more effective ways to get that kind of information. | ||
68 | </p> | ||
69 | |||
70 | <h6 i18n class="p2p-privacy-title">How does PeerTube compare with YouTube?</h6> | ||
71 | |||
72 | <p i18n> | ||
73 | The threats to privacy in YouTube are different from PeerTube's. | ||
74 | In YouTube's case, the platform gathers a huge amount of your personal information (not only your IP) to analyze them and track you. | ||
75 | Moreover, YouTube is owned by Google/Alphabet, a company that tracks you across many websites (via AdSense or Google Analytics). | ||
76 | </p> | ||
77 | |||
78 | <h6 i18n class="p2p-privacy-title">What can I do to limit the exposure of my IP address?</h6> | ||
79 | |||
80 | <p i18n> | ||
81 | Your IP address is public so every time you consult a website, there is a number of actors (in addition to the final website) seeing your IP in their connection logs: ISP/routers/trackers/CDN and more. | ||
82 | PeerTube is transparent about it: we warn you that if you want to keep your IP private, you must use a VPN or Tor Browser. | ||
83 | Thinking that removing P2P from PeerTube will give you back anonymity doesn't make sense. | ||
84 | </p> | ||
85 | |||
86 | <h6 i18n class="p2p-privacy-title">What will be done to mitigate this problem?</h6> | ||
87 | |||
88 | <p i18n> | ||
89 | PeerTube is in its early stages, and want to deliver the best countermeasures possible by the time the stable is released. | ||
90 | In the meantime, we want to test different ideas related to this issue: | ||
91 | </p> | ||
92 | |||
93 | <ul> | ||
94 | <li i18n>Set a limit to the number of peers sent by the tracker</li> | ||
95 | <li i18n>Set a limit on the request frequency received by the tracker (being tested)</li> | ||
96 | <li i18n>Ring a bell if there are unusual requests (being tested)</li> | ||
97 | <li i18n>Disable P2P from the administration interface</li> | ||
98 | <li i18n>An automatic video redundancy program: we wouldn't know if the IP downloaded the video on purpose or if it was the automatized program</li> | ||
99 | </ul> | ||
100 | </div> | ||
19 | 101 | ||
20 | <p i18n> | ||
21 | PeerTube uses the BitTorrent protocol to share bandwidth between users. | ||
22 | This implies that your IP address is stored in the instance's BitTorrent tracker as long as you download or watch the video. | ||
23 | </p> | ||
24 | |||
25 | <h6 i18n class="p2p-privacy-title">What are the consequences?</h6> | ||
26 | |||
27 | <p i18n> | ||
28 | In theory, someone with enough technical skills could create a script that tracks which IP is downloading which video. | ||
29 | In practice, this is much more difficult because: | ||
30 | </p> | ||
31 | |||
32 | <ul> | ||
33 | <li i18n> | ||
34 | An HTTP request has to be sent on each tracker for each video to spy. | ||
35 | If we want to spy all PeerTube's videos, we have to send as many requests as there are videos (so potentially a lot) | ||
36 | </li> | ||
37 | |||
38 | <li i18n> | ||
39 | For each request sent, the tracker returns random peers at a limited number. | ||
40 | For instance, if there are 1000 peers in the swarm and the tracker sends only 20 peers for each request, there must be at least 50 requests sent to know every peers in the swarm | ||
41 | </li> | ||
42 | |||
43 | <li i18n> | ||
44 | Those requests have to be sent regularly to know who starts/stops watching a video. It is easy to detect that kind of behaviour | ||
45 | </li> | ||
46 | |||
47 | <li i18n> | ||
48 | If an IP address is stored in the tracker, it doesn't mean that the person behind the IP (if this person exists) has watched the video | ||
49 | </li> | ||
50 | |||
51 | <li i18n> | ||
52 | The IP address is a vague information : usually, it regularly changes and can represent many persons or entities | ||
53 | </li> | ||
54 | |||
55 | <li i18n> | ||
56 | Web peers are not publicly accessible: because we use WebRTC inside the web browser (<a href="https://webtorrent.io/">with the WebTorrent library</a>), the protocol is different from classic BitTorrent. | ||
57 | When you are in a web browser, you send a signal containing your IP address to the tracker that will randomly choose other peers to forward the information to. | ||
58 | See <a href="https://github.com/yciabaud/webtorrent/blob/beps/bep_webrtc.rst">this document</a> for more information | ||
59 | </li> | ||
60 | </ul> | ||
61 | |||
62 | <p i18n> | ||
63 | The worst-case scenario of an average person spying on their friends is quite unlikely. | ||
64 | There are much more effective ways to get that kind of information. | ||
65 | </p> | ||
66 | |||
67 | <h6 i18n class="p2p-privacy-title">How does PeerTube compare with YouTube?</h6> | ||
68 | |||
69 | <p i18n> | ||
70 | The threats to privacy in YouTube are different from PeerTube's. | ||
71 | In YouTube's case, the platform gathers a huge amount of your personal information (not only your IP) to analyze them and track you. | ||
72 | Moreover, YouTube is owned by Google/Alphabet, a company that tracks you across many websites (via AdSense or Google Analytics). | ||
73 | </p> | ||
74 | |||
75 | <h6 i18n class="p2p-privacy-title">What can I do to limit the exposure of my IP address?</h6> | ||
76 | |||
77 | <p i18n> | ||
78 | Your IP address is public so every time you consult a website, there is a number of actors (in addition to the final website) seeing your IP in their connection logs: ISP/routers/trackers/CDN and more. | ||
79 | PeerTube is transparent about it: we warn you that if you want to keep your IP private, you must use a VPN or Tor Browser. | ||
80 | Thinking that removing P2P from PeerTube will give you back anonymity doesn't make sense. | ||
81 | </p> | ||
82 | |||
83 | <h6 i18n class="p2p-privacy-title">What will be done to mitigate this problem?</h6> | ||
84 | |||
85 | <p i18n> | ||
86 | PeerTube is in its early stages, and want to deliver the best countermeasures possible by the time the stable is released. | ||
87 | In the meantime, we want to test different ideas related to this issue: | ||
88 | </p> | ||
89 | |||
90 | <ul> | ||
91 | <li i18n>Set a limit to the number of peers sent by the tracker</li> | ||
92 | <li i18n>Set a limit on the request frequency received by the tracker (being tested)</li> | ||
93 | <li i18n>Ring a bell if there are unusual requests (being tested)</li> | ||
94 | <li i18n>Disable P2P from the administration interface</li> | ||
95 | <li i18n>An automatic video redundancy program: we wouldn't know if the IP downloaded the video on purpose or if it was the automatized program</li> | ||
96 | </ul> | ||
97 | </div> | 102 | </div> |
diff --git a/client/src/app/+about/about-peertube/about-peertube.component.scss b/client/src/app/+about/about-peertube/about-peertube.component.scss index 0d2e2bb68..8fca53e90 100644 --- a/client/src/app/+about/about-peertube/about-peertube.component.scss +++ b/client/src/app/+about/about-peertube/about-peertube.component.scss | |||
@@ -2,12 +2,12 @@ | |||
2 | @import '_mixins'; | 2 | @import '_mixins'; |
3 | 3 | ||
4 | .about-peertube-title { | 4 | .about-peertube-title { |
5 | font-size: 25px; | 5 | font-size: 20px; |
6 | font-weight: bold; | 6 | font-weight: $font-semibold; |
7 | margin-bottom: 15px; | 7 | margin-bottom: 15px; |
8 | } | 8 | } |
9 | 9 | ||
10 | .section-title { | 10 | /deep/ .section-title { |
11 | font-weight: $font-semibold; | 11 | font-weight: $font-semibold; |
12 | font-size: 20px; | 12 | font-size: 20px; |
13 | margin-bottom: 5px; | 13 | margin-bottom: 5px; |
@@ -17,6 +17,41 @@ | |||
17 | margin-bottom: 30px; | 17 | margin-bottom: 30px; |
18 | } | 18 | } |
19 | 19 | ||
20 | .description, | ||
21 | .p2p-privacy, | ||
22 | my-about-peertube-contributors { | ||
23 | /deep/ { | ||
24 | p, li { | ||
25 | font-size: 15px; | ||
26 | } | ||
27 | } | ||
28 | } | ||
29 | |||
20 | .p2p-privacy-title { | 30 | .p2p-privacy-title { |
21 | margin-top: 15px; | 31 | margin-top: 15px; |
22 | } \ No newline at end of file | 32 | } |
33 | |||
34 | .privacy-contributors { | ||
35 | display: flex; | ||
36 | flex-direction: row; | ||
37 | |||
38 | > div, | ||
39 | > my-about-peertube-contributors { | ||
40 | flex-basis: 100%; | ||
41 | display: block; | ||
42 | } | ||
43 | |||
44 | .p2p-privacy { | ||
45 | h6 { | ||
46 | font-size: 20px; | ||
47 | } | ||
48 | } | ||
49 | |||
50 | my-about-peertube-contributors { | ||
51 | margin: 0 40px 40px 0; | ||
52 | } | ||
53 | |||
54 | @media screen and (max-width: $small-view) { | ||
55 | flex-direction: column; | ||
56 | } | ||
57 | } | ||
diff --git a/client/src/app/+about/about.module.ts b/client/src/app/+about/about.module.ts index 49a7a52f8..14bf76e55 100644 --- a/client/src/app/+about/about.module.ts +++ b/client/src/app/+about/about.module.ts | |||
@@ -1,5 +1,4 @@ | |||
1 | import { NgModule } from '@angular/core' | 1 | import { NgModule } from '@angular/core' |
2 | |||
3 | import { AboutRoutingModule } from './about-routing.module' | 2 | import { AboutRoutingModule } from './about-routing.module' |
4 | import { AboutComponent } from './about.component' | 3 | import { AboutComponent } from './about.component' |
5 | import { SharedModule } from '../shared' | 4 | import { SharedModule } from '../shared' |
@@ -7,6 +6,7 @@ import { AboutInstanceComponent } from '@app/+about/about-instance/about-instanc | |||
7 | import { AboutPeertubeComponent } from '@app/+about/about-peertube/about-peertube.component' | 6 | import { AboutPeertubeComponent } from '@app/+about/about-peertube/about-peertube.component' |
8 | import { ContactAdminModalComponent } from '@app/+about/about-instance/contact-admin-modal.component' | 7 | import { ContactAdminModalComponent } from '@app/+about/about-instance/contact-admin-modal.component' |
9 | import { AboutFollowsComponent } from '@app/+about/about-follows/about-follows.component' | 8 | import { AboutFollowsComponent } from '@app/+about/about-follows/about-follows.component' |
9 | import { AboutPeertubeContributorsComponent } from '@app/+about/about-peertube/about-peertube-contributors.component' | ||
10 | 10 | ||
11 | @NgModule({ | 11 | @NgModule({ |
12 | imports: [ | 12 | imports: [ |
@@ -19,6 +19,7 @@ import { AboutFollowsComponent } from '@app/+about/about-follows/about-follows.c | |||
19 | AboutInstanceComponent, | 19 | AboutInstanceComponent, |
20 | AboutPeertubeComponent, | 20 | AboutPeertubeComponent, |
21 | AboutFollowsComponent, | 21 | AboutFollowsComponent, |
22 | AboutPeertubeContributorsComponent, | ||
22 | ContactAdminModalComponent | 23 | ContactAdminModalComponent |
23 | ], | 24 | ], |
24 | 25 | ||
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html index fe9d856d0..54115055a 100644 --- a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html +++ b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html | |||
@@ -2,12 +2,13 @@ | |||
2 | 2 | ||
3 | <ngb-tabset class="root-tabset bootstrap"> | 3 | <ngb-tabset class="root-tabset bootstrap"> |
4 | 4 | ||
5 | <ngb-tab i18n-title title="Basic configuration"> | 5 | <ngb-tab i18n-title title="Instance information"> |
6 | <ng-template ngbTabContent> | 6 | <ng-template ngbTabContent> |
7 | 7 | ||
8 | <div i18n class="inner-form-title">Instance</div> | ||
9 | |||
10 | <ng-container formGroupName="instance"> | 8 | <ng-container formGroupName="instance"> |
9 | |||
10 | <div i18n class="inner-form-title">Instance</div> | ||
11 | |||
11 | <div class="form-group"> | 12 | <div class="form-group"> |
12 | <label i18n for="instanceName">Name</label> | 13 | <label i18n for="instanceName">Name</label> |
13 | <input | 14 | <input |
@@ -20,7 +21,7 @@ | |||
20 | <div class="form-group"> | 21 | <div class="form-group"> |
21 | <label i18n for="instanceShortDescription">Short description</label> | 22 | <label i18n for="instanceShortDescription">Short description</label> |
22 | <textarea | 23 | <textarea |
23 | id="instanceShortDescription" formControlName="shortDescription" | 24 | id="instanceShortDescription" formControlName="shortDescription" class="small" |
24 | [ngClass]="{ 'input-error': formErrors['instance.shortDescription'] }" | 25 | [ngClass]="{ 'input-error': formErrors['instance.shortDescription'] }" |
25 | ></textarea> | 26 | ></textarea> |
26 | <div *ngIf="formErrors.instance.shortDescription" class="form-error">{{ formErrors.instance.shortDescription }}</div> | 27 | <div *ngIf="formErrors.instance.shortDescription" class="form-error">{{ formErrors.instance.shortDescription }}</div> |
@@ -36,42 +37,56 @@ | |||
36 | </div> | 37 | </div> |
37 | 38 | ||
38 | <div class="form-group"> | 39 | <div class="form-group"> |
39 | <label i18n for="instanceTerms">Terms</label><my-help helpType="markdownText"></my-help> | 40 | <label i18n for="instanceCategories">Main instance categories</label> |
40 | <my-markdown-textarea | 41 | |
41 | id="instanceTerms" formControlName="terms" textareaWidth="500px" [previewColumn]="true" | 42 | <div> |
42 | [ngClass]="{ 'input-error': formErrors['instance.terms'] }" | 43 | <p-multiSelect |
43 | ></my-markdown-textarea> | 44 | inputId="instanceCategories" [options]="categoryItems" formControlName="categories" showToggleAll="false" |
44 | <div *ngIf="formErrors.instance.terms" class="form-error">{{ formErrors.instance.terms }}</div> | 45 | [defaultLabel]="getDefaultCategoryLabel()" [selectedItemsLabel]="getSelectedCategoryLabel()" |
46 | emptyFilterMessage="No results found" i18n-emptyFilterMessage | ||
47 | ></p-multiSelect> | ||
48 | </div> | ||
45 | </div> | 49 | </div> |
46 | 50 | ||
47 | <div class="form-group"> | 51 | <div class="form-group"> |
48 | <my-peertube-checkbox | 52 | <label i18n for="instanceLanguages">Main languages you/your moderators speak</label> |
49 | inputName="instanceIsNSFW" formControlName="isNSFW" | 53 | |
50 | i18n-labelText labelText="Dedicated to sensitive or NSFW content" | 54 | <div> |
51 | i18n-helpHtml helpHtml="Enabling it will allow other administrators to know that you are mainly federating sensitive content.<br /><br /> | 55 | <p-multiSelect |
52 | Moreover, the NSFW checkbox on video upload will be automatically checked by default." | 56 | inputId="instanceLanguages" [options]="languageItems" formControlName="languages" showToggleAll="false" |
53 | ></my-peertube-checkbox> | 57 | [defaultLabel]="getDefaultLanguageLabel()" [selectedItemsLabel]="getSelectedLanguageLabel()" |
58 | emptyFilterMessage="No results found" i18n-emptyFilterMessage | ||
59 | ></p-multiSelect> | ||
60 | </div> | ||
54 | </div> | 61 | </div> |
55 | 62 | ||
63 | <div i18n class="inner-form-title">Moderation & NSFW</div> | ||
64 | |||
56 | <div class="form-group"> | 65 | <div class="form-group"> |
57 | <label i18n for="instanceDefaultClientRoute">Default client route</label> | 66 | <my-peertube-checkbox inputName="instanceIsNSFW" formControlName="isNSFW"> |
58 | <div class="peertube-select-container"> | 67 | <ng-template ptTemplate="label"> |
59 | <select id="instanceDefaultClientRoute" formControlName="defaultClientRoute"> | 68 | <ng-container i18n>This instance is dedicated to sensitive or NSFW content</ng-container> |
60 | <option i18n value="/videos/overview">Videos Overview</option> | 69 | </ng-template> |
61 | <option i18n value="/videos/trending">Videos Trending</option> | 70 | |
62 | <option i18n value="/videos/recently-added">Videos Recently Added</option> | 71 | <ng-template ptTemplate="help"> |
63 | <option i18n value="/videos/local">Local videos</option> | 72 | <ng-container i18n> |
64 | </select> | 73 | Enabling it will allow other administrators to know that you are mainly federating sensitive content.<br /><br /> |
65 | </div> | 74 | Moreover, the NSFW checkbox on video upload will be automatically checked by default. |
66 | <div *ngIf="formErrors.instance.defaultClientRoute" class="form-error">{{ formErrors.instance.defaultClientRoute }}</div> | 75 | </ng-container> |
76 | </ng-template> | ||
77 | </my-peertube-checkbox> | ||
67 | </div> | 78 | </div> |
68 | 79 | ||
69 | <div class="form-group"> | 80 | <div class="form-group"> |
70 | <label i18n for="instanceDefaultNSFWPolicy">Policy on videos containing sensitive content</label> | 81 | <label i18n for="instanceDefaultNSFWPolicy">Policy on videos containing sensitive content</label> |
71 | <my-help | 82 | |
72 | helpType="custom" i18n-customHtml | 83 | <my-help> |
73 | customHtml="With <strong>Do not list</strong> or <strong>Blur thumbnails</strong>, a confirmation will be requested to watch the video." | 84 | <ng-template ptTemplate="customHtml"> |
74 | ></my-help> | 85 | <ng-container i18n> |
86 | With <strong>Do not list</strong> or <strong>Blur thumbnails</strong>, a confirmation will be requested to watch the video. | ||
87 | </ng-container> | ||
88 | </ng-template> | ||
89 | </my-help> | ||
75 | 90 | ||
76 | <div class="peertube-select-container"> | 91 | <div class="peertube-select-container"> |
77 | <select id="instanceDefaultNSFWPolicy" formControlName="defaultNSFWPolicy"> | 92 | <select id="instanceDefaultNSFWPolicy" formControlName="defaultNSFWPolicy"> |
@@ -82,10 +97,105 @@ | |||
82 | </div> | 97 | </div> |
83 | <div *ngIf="formErrors.instance.defaultNSFWPolicy" class="form-error">{{ formErrors.instance.defaultNSFWPolicy }}</div> | 98 | <div *ngIf="formErrors.instance.defaultNSFWPolicy" class="form-error">{{ formErrors.instance.defaultNSFWPolicy }}</div> |
84 | </div> | 99 | </div> |
100 | |||
101 | <div class="form-group"> | ||
102 | <label i18n for="instanceTerms">Terms</label><my-help helpType="markdownText"></my-help> | ||
103 | <my-markdown-textarea | ||
104 | id="instanceTerms" formControlName="terms" textareaWidth="500px" [previewColumn]="true" | ||
105 | [ngClass]="{ 'input-error': formErrors['instance.terms'] }" | ||
106 | ></my-markdown-textarea> | ||
107 | <div *ngIf="formErrors.instance.terms" class="form-error">{{ formErrors.instance.terms }}</div> | ||
108 | </div> | ||
109 | |||
110 | <div class="form-group"> | ||
111 | <label i18n for="instanceCodeOfConduct">Code of conduct</label><my-help helpType="markdownText"></my-help> | ||
112 | <my-markdown-textarea | ||
113 | id="instanceCodeOfConduct" formControlName="codeOfConduct" textareaWidth="500px" [previewColumn]="true" | ||
114 | [ngClass]="{ 'input-error': formErrors['instance.codeOfConduct'] }" | ||
115 | ></my-markdown-textarea> | ||
116 | <div *ngIf="formErrors.instance.codeOfConduct" class="form-error">{{ formErrors.instance.codeOfConduct }}</div> | ||
117 | </div> | ||
118 | |||
119 | <div class="form-group"> | ||
120 | <label i18n for="instanceModerationInformation">Moderation information</label><my-help helpType="markdownText"></my-help> | ||
121 | <div class="label-small-info">Who moderates the instance? What is the policy regarding NSFW videos? Political videos? etc</div> | ||
122 | |||
123 | <my-markdown-textarea | ||
124 | id="instanceModerationInformation" formControlName="moderationInformation" textareaWidth="500px" [previewColumn]="true" | ||
125 | [ngClass]="{ 'input-error': formErrors['instance.moderationInformation'] }" | ||
126 | ></my-markdown-textarea> | ||
127 | <div *ngIf="formErrors.instance.moderationInformation" class="form-error">{{ formErrors.instance.moderationInformation }}</div> | ||
128 | </div> | ||
129 | |||
130 | <div i18n class="inner-form-title">You and your instance</div> | ||
131 | |||
132 | <div class="form-group"> | ||
133 | <label i18n for="instanceAdministrator">Who is behind the instance?</label> | ||
134 | <div class="label-small-info">A single person? A non profit? A company?</div> | ||
135 | |||
136 | <my-markdown-textarea | ||
137 | id="instanceAdministrator" formControlName="administrator" textareaWidth="500px" textareaHeight="75px" [previewColumn]="true" | ||
138 | [classes]="{ 'input-error': formErrors['instance.administrator'] }" | ||
139 | ></my-markdown-textarea> | ||
140 | |||
141 | <div *ngIf="formErrors.instance.administrator" class="form-error">{{ formErrors.instance.administrator }}</div> | ||
142 | </div> | ||
143 | |||
144 | <div class="form-group"> | ||
145 | <label i18n for="instanceCreationReason">Why did you create this instance?</label> | ||
146 | <div class="label-small-info">To share your personal videos? To open registrations and allow people to upload what they want?</div> | ||
147 | |||
148 | <textarea | ||
149 | id="instanceCreationReason" formControlName="creationReason" class="small" | ||
150 | [ngClass]="{ 'input-error': formErrors['instance.creationReason'] }" | ||
151 | ></textarea> | ||
152 | <div *ngIf="formErrors.instance.creationReason" class="form-error">{{ formErrors.instance.creationReason }}</div> | ||
153 | </div> | ||
154 | |||
155 | <div class="form-group"> | ||
156 | <label i18n for="instanceMaintenanceLifetime">How long do you plan to maintain this instance?</label> | ||
157 | <div class="label-small-info">It's important to know for users who want to register on your instance</div> | ||
158 | |||
159 | <textarea | ||
160 | id="instanceMaintenanceLifetime" formControlName="maintenanceLifetime" class="small" | ||
161 | [ngClass]="{ 'input-error': formErrors['instance.maintenanceLifetime'] }" | ||
162 | ></textarea> | ||
163 | <div *ngIf="formErrors.instance.maintenanceLifetime" class="form-error">{{ formErrors.instance.maintenanceLifetime }}</div> | ||
164 | </div> | ||
165 | |||
166 | <div class="form-group"> | ||
167 | <label i18n for="instanceBusinessModel">How will you pay the PeerTube instance server?</label> | ||
168 | <div class="label-small-info">With you own funds? With users donations? Advertising?</div> | ||
169 | |||
170 | <textarea | ||
171 | id="instanceBusinessModel" formControlName="businessModel" class="small" | ||
172 | [ngClass]="{ 'input-error': formErrors['instance.businessModel'] }" | ||
173 | ></textarea> | ||
174 | <div *ngIf="formErrors.instance.businessModel" class="form-error">{{ formErrors.instance.businessModel }}</div> | ||
175 | </div> | ||
176 | |||
177 | <div i18n class="inner-form-title">Other information</div> | ||
178 | |||
179 | <div class="form-group"> | ||
180 | <label i18n for="instanceHardwareInformation">On what server/hardware the instance runs?</label> | ||
181 | <div class="label-small-info">2vCore 2GB RAM/or directly the link to the server you rent etc</div> | ||
182 | |||
183 | <my-markdown-textarea | ||
184 | id="instanceHardwareInformation" formControlName="hardwareInformation" textareaWidth="500px" textareaHeight="75px" [previewColumn]="true" | ||
185 | [classes]="{ 'input-error': formErrors['instance.hardwareInformation'] }" | ||
186 | ></my-markdown-textarea> | ||
187 | |||
188 | <div *ngIf="formErrors.instance.hardwareInformation" class="form-error">{{ formErrors.instance.hardwareInformation }}</div> | ||
189 | </div> | ||
190 | |||
85 | </ng-container> | 191 | </ng-container> |
192 | </ng-template> | ||
193 | </ngb-tab> | ||
86 | 194 | ||
195 | <ngb-tab i18n-title title="Basic configuration"> | ||
196 | <ng-template ngbTabContent> | ||
87 | 197 | ||
88 | <div i18n class="inner-form-title">Theme</div> | 198 | <div i18n class="inner-form-title">Theme & Default route</div> |
89 | 199 | ||
90 | <ng-container formGroupName="theme"> | 200 | <ng-container formGroupName="theme"> |
91 | <div class="form-group"> | 201 | <div class="form-group"> |
@@ -102,6 +212,19 @@ | |||
102 | </ng-container> | 212 | </ng-container> |
103 | 213 | ||
104 | 214 | ||
215 | <div class="form-group" formGroupName="instance"> | ||
216 | <label i18n for="instanceDefaultClientRoute">Default client route</label> | ||
217 | <div class="peertube-select-container"> | ||
218 | <select id="instanceDefaultClientRoute" formControlName="defaultClientRoute"> | ||
219 | <option i18n value="/videos/overview">Videos Discover</option> | ||
220 | <option i18n value="/videos/trending">Videos Trending</option> | ||
221 | <option i18n value="/videos/recently-added">Videos Recently Added</option> | ||
222 | <option i18n value="/videos/local">Local videos</option> | ||
223 | </select> | ||
224 | </div> | ||
225 | <div *ngIf="formErrors.instance.defaultClientRoute" class="form-error">{{ formErrors.instance.defaultClientRoute }}</div> | ||
226 | </div> | ||
227 | |||
105 | <div i18n class="inner-form-title">Signup</div> | 228 | <div i18n class="inner-form-title">Signup</div> |
106 | 229 | ||
107 | <ng-container formGroupName="signup"> | 230 | <ng-container formGroupName="signup"> |
@@ -221,6 +344,41 @@ | |||
221 | </ng-container> | 344 | </ng-container> |
222 | </ng-container> | 345 | </ng-container> |
223 | 346 | ||
347 | <div i18n class="inner-form-title">Instance followings</div> | ||
348 | |||
349 | <ng-container formGroupName="followings"> | ||
350 | <ng-container formGroupName="instance"> | ||
351 | |||
352 | <ng-container formGroupName="autoFollowBack"> | ||
353 | <div class="form-group"> | ||
354 | <my-peertube-checkbox | ||
355 | inputName="followingsInstanceAutoFollowBackEnabled" formControlName="enabled" | ||
356 | i18n-labelText labelText="Automatically follow other instances that follow you" | ||
357 | ></my-peertube-checkbox> | ||
358 | </div> | ||
359 | </ng-container> | ||
360 | |||
361 | <ng-container formGroupName="autoFollowIndex"> | ||
362 | <div class="form-group"> | ||
363 | <my-peertube-checkbox | ||
364 | inputName="followingsInstanceAutoFollowIndexEnabled" formControlName="enabled" | ||
365 | i18n-labelText labelText="Automatically follow instance of the public index (below)" | ||
366 | ></my-peertube-checkbox> | ||
367 | </div> | ||
368 | |||
369 | <div class="form-group"> | ||
370 | <label i18n for="followingsInstanceAutoFollowIndexUrl">Index URL</label> | ||
371 | <input | ||
372 | type="text" id="followingsInstanceAutoFollowIndexUrl" | ||
373 | formControlName="indexUrl" [ngClass]="{ 'input-error': formErrors['followings.instance.autoFollowIndex.indexUrl'] }" | ||
374 | > | ||
375 | <div *ngIf="formErrors.followings.instance.autoFollowIndex.indexUrl" class="form-error">{{ formErrors.followings.instance.autoFollowIndex.indexUrl }}</div> | ||
376 | </div> | ||
377 | |||
378 | </ng-container> | ||
379 | </ng-container> | ||
380 | </ng-container> | ||
381 | |||
224 | 382 | ||
225 | <div i18n class="inner-form-title">Administrator</div> | 383 | <div i18n class="inner-form-title">Administrator</div> |
226 | 384 | ||
@@ -252,10 +410,13 @@ | |||
252 | 410 | ||
253 | <div class="form-group"> | 411 | <div class="form-group"> |
254 | <label i18n for="signupLimit">Your Twitter username</label> | 412 | <label i18n for="signupLimit">Your Twitter username</label> |
255 | <my-help | 413 | |
256 | helpType="custom" i18n-customHtml | 414 | <my-help> |
257 | customHtml="Indicates the Twitter account for the website or platform on which the content was published." | 415 | <ng-template ptTemplate="customHtml"> |
258 | ></my-help> | 416 | <ng-container i18n>Indicates the Twitter account for the website or platform on which the content was published.</ng-container> |
417 | </ng-template> | ||
418 | </my-help> | ||
419 | |||
259 | <input | 420 | <input |
260 | type="text" id="servicesTwitterUsername" | 421 | type="text" id="servicesTwitterUsername" |
261 | formControlName="username" [ngClass]="{ 'input-error': formErrors['services.twitter.username'] }" | 422 | formControlName="username" [ngClass]="{ 'input-error': formErrors['services.twitter.username'] }" |
@@ -264,13 +425,21 @@ | |||
264 | </div> | 425 | </div> |
265 | 426 | ||
266 | <div class="form-group"> | 427 | <div class="form-group"> |
267 | <my-peertube-checkbox | 428 | <my-peertube-checkbox inputName="servicesTwitterWhitelisted" formControlName="whitelisted"> |
268 | inputName="servicesTwitterWhitelisted" formControlName="whitelisted" | 429 | <ng-template ptTemplate="label"> |
269 | i18n-labelText labelText="Instance whitelisted by Twitter" | 430 | <ng-container i18n>Instance whitelisted by Twitter</ng-container> |
270 | i18n-helpHtml helpHtml="If your instance is whitelisted by Twitter, a video player will be embedded in the Twitter feed on PeerTube video share.<br /> | 431 | </ng-template> |
271 | If the instance is not whitelisted, we use an image link card that will redirect on your PeerTube instance.<br /><br /> | 432 | |
272 | Check this checkbox, save the configuration and test with a video URL of your instance (https://example.com/videos/watch/blabla) on <a target='_blank' rel='noopener noreferrer' href='https://cards-dev.twitter.com/validator'>https://cards-dev.twitter.com/validator</a> to see if you instance is whitelisted." | 433 | <ng-template ptTemplate="help"> |
273 | ></my-peertube-checkbox> | 434 | <ng-container i18n> |
435 | If your instance is whitelisted by Twitter, a video player will be embedded in the Twitter feed on PeerTube video share.<br /> | ||
436 | If the instance is not whitelisted, we use an image link card that will redirect on your PeerTube instance.<br /><br /> | ||
437 | Check this checkbox, save the configuration and test with a video URL of your instance (https://example.com/videos/watch/blabla) on | ||
438 | <a target='_blank' rel='noopener noreferrer' href='https://cards-dev.twitter.com/validator'>https://cards-dev.twitter.com/validator</a> | ||
439 | to see if you instance is whitelisted. | ||
440 | </ng-container> | ||
441 | </ng-template> | ||
442 | </my-peertube-checkbox> | ||
274 | </div> | 443 | </div> |
275 | 444 | ||
276 | </ng-container> | 445 | </ng-container> |
@@ -286,11 +455,15 @@ | |||
286 | 455 | ||
287 | <ng-container formGroupName="transcoding"> | 456 | <ng-container formGroupName="transcoding"> |
288 | <div class="form-group"> | 457 | <div class="form-group"> |
289 | <my-peertube-checkbox | 458 | <my-peertube-checkbox inputName="transcodingEnabled" formControlName="enabled"> |
290 | inputName="transcodingEnabled" formControlName="enabled" | 459 | <ng-template ptTemplate="label"> |
291 | i18n-labelText labelText="Transcoding enabled" | 460 | <ng-container i18n>Transcoding enabled</ng-container> |
292 | i18n-helpHtml helpHtml="If you disable transcoding, many videos from your users will not work!" | 461 | </ng-template> |
293 | ></my-peertube-checkbox> | 462 | |
463 | <ng-template ptTemplate="help"> | ||
464 | <ng-container i18n>If you disable transcoding, many videos from your users will not work!</ng-container> | ||
465 | </ng-template> | ||
466 | </my-peertube-checkbox> | ||
294 | </div> | 467 | </div> |
295 | 468 | ||
296 | <ng-container *ngIf="isTranscodingEnabled()"> | 469 | <ng-container *ngIf="isTranscodingEnabled()"> |
@@ -299,16 +472,22 @@ | |||
299 | <my-peertube-checkbox | 472 | <my-peertube-checkbox |
300 | inputName="transcodingAllowAdditionalExtensions" formControlName="allowAdditionalExtensions" | 473 | inputName="transcodingAllowAdditionalExtensions" formControlName="allowAdditionalExtensions" |
301 | i18n-labelText labelText="Allow additional extensions" | 474 | i18n-labelText labelText="Allow additional extensions" |
302 | i18n-helpHtml helpHtml="Allow your users to upload .mkv, .mov, .avi, .flv videos" | 475 | > |
303 | ></my-peertube-checkbox> | 476 | <ng-template ptTemplate="help"> |
477 | <ng-container i18n>Allow your users to upload .mkv, .mov, .avi, .flv videos</ng-container> | ||
478 | </ng-template> | ||
479 | </my-peertube-checkbox> | ||
304 | </div> | 480 | </div> |
305 | 481 | ||
306 | <div class="form-group"> | 482 | <div class="form-group"> |
307 | <my-peertube-checkbox | 483 | <my-peertube-checkbox |
308 | inputName="transcodingAllowAudioFiles" formControlName="allowAudioFiles" | 484 | inputName="transcodingAllowAudioFiles" formControlName="allowAudioFiles" |
309 | i18n-labelText labelText="Allow audio files upload" | 485 | i18n-labelText labelText="Allow audio files upload" |
310 | i18n-helpHtml helpHtml="Allow your users to upload audio files that will be merged with the preview file on upload" | 486 | > |
311 | ></my-peertube-checkbox> | 487 | <ng-template ptTemplate="help"> |
488 | <ng-container i18n>Allow your users to upload audio files that will be merged with the preview file on upload</ng-container> | ||
489 | </ng-template> | ||
490 | </my-peertube-checkbox> | ||
312 | </div> | 491 | </div> |
313 | 492 | ||
314 | <div class="form-group"> | 493 | <div class="form-group"> |
@@ -338,10 +517,11 @@ | |||
338 | <div i18n class="inner-form-title"> | 517 | <div i18n class="inner-form-title"> |
339 | Cache | 518 | Cache |
340 | 519 | ||
341 | <my-help | 520 | <my-help> |
342 | helpType="custom" i18n-customHtml | 521 | <ng-template ptTemplate="customHtml"> |
343 | customHtml="Some files are not federated (previews, captions). We fetch them directly from the origin instance and cache them." | 522 | <ng-container i18n>Some files are not federated (previews, captions). We fetch them directly from the origin instance and cache them.</ng-container> |
344 | ></my-help> | 523 | </ng-template> |
524 | </my-help> | ||
345 | </div> | 525 | </div> |
346 | 526 | ||
347 | <ng-container formGroupName="cache"> | 527 | <ng-container formGroupName="cache"> |
@@ -370,38 +550,45 @@ | |||
370 | <ng-container formGroupName="customizations"> | 550 | <ng-container formGroupName="customizations"> |
371 | <div class="form-group"> | 551 | <div class="form-group"> |
372 | <label i18n for="customizationJavascript">JavaScript</label> | 552 | <label i18n for="customizationJavascript">JavaScript</label> |
373 | <my-help | 553 | <my-help> |
374 | helpType="custom" i18n-customHtml | 554 | <ng-template ptTemplate="customHtml"> |
375 | customHtml="Write directly JavaScript code.<br />Example: <pre>console.log('my instance is amazing');</pre>" | 555 | <ng-container i18n> |
376 | ></my-help> | 556 | Write directly JavaScript code.<br />Example: <pre>console.log('my instance is amazing');</pre> |
557 | </ng-container> | ||
558 | </ng-template> | ||
559 | </my-help> | ||
560 | |||
377 | <textarea | 561 | <textarea |
378 | id="customizationJavascript" formControlName="javascript" | 562 | id="customizationJavascript" formControlName="javascript" |
379 | [ngClass]="{ 'input-error': formErrors['instance.customizations.javascript'] }" | 563 | [ngClass]="{ 'input-error': formErrors['instance.customizations.javascript'] }" |
380 | ></textarea> | 564 | ></textarea> |
565 | |||
381 | <div *ngIf="formErrors.instance.customizations.javascript" class="form-error">{{ formErrors.instance.customizations.javascript }}</div> | 566 | <div *ngIf="formErrors.instance.customizations.javascript" class="form-error">{{ formErrors.instance.customizations.javascript }}</div> |
382 | </div> | 567 | </div> |
383 | 568 | ||
384 | <div class="form-group"> | 569 | <div class="form-group"> |
385 | <label for="customizationCSS">CSS</label> | 570 | <label for="customizationCSS">CSS</label> |
386 | <my-help | 571 | |
387 | helpType="custom" | 572 | <my-help> |
388 | i18n-customHtml | 573 | <ng-template ptTemplate="customHtml"> |
389 | customHtml=" | 574 | <ng-container i18n> |
390 | Write directly CSS code. Example:<br /><br /> | 575 | Write directly CSS code. Example:<br /><br /> |
391 | <pre> | 576 | <pre> |
392 | #custom-css {{ '{' }} | 577 | #custom-css {{ '{' }} |
393 | color: red; | 578 | color: red; |
394 | {{ '}' }} | 579 | {{ '}' }} |
395 | </pre> | 580 | </pre> |
396 | 581 | ||
397 | Prepend with <em>#custom-css</em> to override styles. Example:<br /><br /> | 582 | Prepend with <em>#custom-css</em> to override styles. Example:<br /><br /> |
398 | <pre> | 583 | <pre> |
399 | #custom-css .logged-in-email {{ '{' }} | 584 | #custom-css .logged-in-email {{ '{' }} |
400 | color: red; | 585 | color: red; |
401 | {{ '}' }} | 586 | {{ '}' }} |
402 | </pre> | 587 | </pre> |
403 | " | 588 | </ng-container> |
404 | ></my-help> | 589 | </ng-template> |
590 | </my-help> | ||
591 | |||
405 | <textarea | 592 | <textarea |
406 | id="customizationCSS" formControlName="css" | 593 | id="customizationCSS" formControlName="css" |
407 | [ngClass]="{ 'input-error': formErrors['instance.customizations.css'] }" | 594 | [ngClass]="{ 'input-error': formErrors['instance.customizations.css'] }" |
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.scss b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.scss index c90bd5141..2b4d0da2c 100644 --- a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.scss +++ b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.scss | |||
@@ -1,6 +1,10 @@ | |||
1 | @import '_variables'; | 1 | @import '_variables'; |
2 | @import '_mixins'; | 2 | @import '_mixins'; |
3 | 3 | ||
4 | .form-group { | ||
5 | margin-bottom: 25px; | ||
6 | } | ||
7 | |||
4 | input[type=text] { | 8 | input[type=text] { |
5 | @include peertube-input-text(340px); | 9 | @include peertube-input-text(340px); |
6 | display: block; | 10 | display: block; |
@@ -40,7 +44,12 @@ textarea { | |||
40 | 44 | ||
41 | display: block; | 45 | display: block; |
42 | 46 | ||
43 | &#instanceShortDescription { | 47 | &.small { |
44 | height: 100px; | 48 | height: 75px; |
45 | } | 49 | } |
46 | } | 50 | } |
51 | |||
52 | .label-small-info { | ||
53 | font-style: italic; | ||
54 | margin-bottom: 10px; | ||
55 | } | ||
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts index 8bd7f7cf6..0a69f3481 100644 --- a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts +++ b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts | |||
@@ -6,6 +6,9 @@ import { Notifier } from '@app/core' | |||
6 | import { CustomConfig } from '../../../../../../shared/models/server/custom-config.model' | 6 | import { CustomConfig } from '../../../../../../shared/models/server/custom-config.model' |
7 | import { I18n } from '@ngx-translate/i18n-polyfill' | 7 | import { I18n } from '@ngx-translate/i18n-polyfill' |
8 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' | 8 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' |
9 | import { SelectItem } from 'primeng/api' | ||
10 | import { forkJoin } from 'rxjs' | ||
11 | import { first } from 'rxjs/operators' | ||
9 | 12 | ||
10 | @Component({ | 13 | @Component({ |
11 | selector: 'my-edit-custom-config', | 14 | selector: 'my-edit-custom-config', |
@@ -18,6 +21,9 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit { | |||
18 | resolutions: { id: string, label: string }[] = [] | 21 | resolutions: { id: string, label: string }[] = [] |
19 | transcodingThreadOptions: { label: string, value: number }[] = [] | 22 | transcodingThreadOptions: { label: string, value: number }[] = [] |
20 | 23 | ||
24 | languageItems: SelectItem[] = [] | ||
25 | categoryItems: SelectItem[] = [] | ||
26 | |||
21 | constructor ( | 27 | constructor ( |
22 | protected formValidatorService: FormValidatorService, | 28 | protected formValidatorService: FormValidatorService, |
23 | private customConfigValidatorsService: CustomConfigValidatorsService, | 29 | private customConfigValidatorsService: CustomConfigValidatorsService, |
@@ -88,10 +94,26 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit { | |||
88 | name: this.customConfigValidatorsService.INSTANCE_NAME, | 94 | name: this.customConfigValidatorsService.INSTANCE_NAME, |
89 | shortDescription: this.customConfigValidatorsService.INSTANCE_SHORT_DESCRIPTION, | 95 | shortDescription: this.customConfigValidatorsService.INSTANCE_SHORT_DESCRIPTION, |
90 | description: null, | 96 | description: null, |
91 | terms: null, | 97 | |
92 | defaultClientRoute: null, | ||
93 | isNSFW: false, | 98 | isNSFW: false, |
94 | defaultNSFWPolicy: null, | 99 | defaultNSFWPolicy: null, |
100 | |||
101 | terms: null, | ||
102 | codeOfConduct: null, | ||
103 | |||
104 | creationReason: null, | ||
105 | moderationInformation: null, | ||
106 | administrator: null, | ||
107 | maintenanceLifetime: null, | ||
108 | businessModel: null, | ||
109 | |||
110 | hardwareInformation: null, | ||
111 | |||
112 | categories: null, | ||
113 | languages: null, | ||
114 | |||
115 | defaultClientRoute: null, | ||
116 | |||
95 | customizations: { | 117 | customizations: { |
96 | javascript: null, | 118 | javascript: null, |
97 | css: null | 119 | css: null |
@@ -158,6 +180,17 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit { | |||
158 | enabled: null, | 180 | enabled: null, |
159 | manualApproval: null | 181 | manualApproval: null |
160 | } | 182 | } |
183 | }, | ||
184 | followings: { | ||
185 | instance: { | ||
186 | autoFollowBack: { | ||
187 | enabled: null | ||
188 | }, | ||
189 | autoFollowIndex: { | ||
190 | enabled: null, | ||
191 | indexUrl: this.customConfigValidatorsService.INDEX_URL | ||
192 | } | ||
193 | } | ||
161 | } | 194 | } |
162 | } | 195 | } |
163 | 196 | ||
@@ -173,18 +206,27 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit { | |||
173 | 206 | ||
174 | this.buildForm(formGroupData) | 207 | this.buildForm(formGroupData) |
175 | 208 | ||
176 | this.configService.getCustomConfig() | 209 | forkJoin([ |
177 | .subscribe( | 210 | this.configService.getCustomConfig(), |
178 | res => { | 211 | this.serverService.videoLanguagesLoaded.pipe(first()), // First so the observable completes |
179 | this.customConfig = res | 212 | this.serverService.videoCategoriesLoaded.pipe(first()) |
213 | ]).subscribe( | ||
214 | ([ config ]) => { | ||
215 | this.customConfig = config | ||
180 | 216 | ||
181 | this.updateForm() | 217 | const languages = this.serverService.getVideoLanguages() |
182 | // Force form validation | 218 | this.languageItems = languages.map(l => ({ label: l.label, value: l.id })) |
183 | this.forceCheck() | ||
184 | }, | ||
185 | 219 | ||
186 | err => this.notifier.error(err.message) | 220 | const categories = this.serverService.getVideoCategories() |
187 | ) | 221 | this.categoryItems = categories.map(l => ({ label: l.label, value: l.id })) |
222 | |||
223 | this.updateForm() | ||
224 | // Force form validation | ||
225 | this.forceCheck() | ||
226 | }, | ||
227 | |||
228 | err => this.notifier.error(err.message) | ||
229 | ) | ||
188 | } | 230 | } |
189 | 231 | ||
190 | isTranscodingEnabled () { | 232 | isTranscodingEnabled () { |
@@ -213,8 +255,23 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit { | |||
213 | ) | 255 | ) |
214 | } | 256 | } |
215 | 257 | ||
258 | getSelectedLanguageLabel () { | ||
259 | return this.i18n('{{\'{0} languages selected') | ||
260 | } | ||
261 | |||
262 | getDefaultLanguageLabel () { | ||
263 | return this.i18n('No language') | ||
264 | } | ||
265 | |||
266 | getSelectedCategoryLabel () { | ||
267 | return this.i18n('{{\'{0} categories selected') | ||
268 | } | ||
269 | |||
270 | getDefaultCategoryLabel () { | ||
271 | return this.i18n('No category') | ||
272 | } | ||
273 | |||
216 | private updateForm () { | 274 | private updateForm () { |
217 | this.form.patchValue(this.customConfig) | 275 | this.form.patchValue(this.customConfig) |
218 | } | 276 | } |
219 | |||
220 | } | 277 | } |
diff --git a/client/src/app/+admin/system/debug/debug.component.html b/client/src/app/+admin/system/debug/debug.component.html index f35414b37..75f3df601 100644 --- a/client/src/app/+admin/system/debug/debug.component.html +++ b/client/src/app/+admin/system/debug/debug.component.html | |||
@@ -1,7 +1,7 @@ | |||
1 | <div class="root"> | 1 | <div class="root"> |
2 | <h4>IP</h4> | 2 | <h4>IP</h4> |
3 | 3 | ||
4 | <p>PeerTube thinks your public IP is <strong>{{ debug?.ip }}</strong>.</p> | 4 | <p>PeerTube thinks your web browser public IP is <strong>{{ debug?.ip }}</strong>.</p> |
5 | 5 | ||
6 | <p>If this is not your correct public IP, please consider fixing it because:</p> | 6 | <p>If this is not your correct public IP, please consider fixing it because:</p> |
7 | <ul> | 7 | <ul> |
diff --git a/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.ts b/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.ts index 34febc457..76fabb19d 100644 --- a/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.ts +++ b/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.ts | |||
@@ -43,7 +43,8 @@ export class MyAccountNotificationPreferencesComponent implements OnInit { | |||
43 | newUserRegistration: this.i18n('A new user registered on your instance'), | 43 | newUserRegistration: this.i18n('A new user registered on your instance'), |
44 | newFollow: this.i18n('You or your channel(s) has a new follower'), | 44 | newFollow: this.i18n('You or your channel(s) has a new follower'), |
45 | commentMention: this.i18n('Someone mentioned you in video comments'), | 45 | commentMention: this.i18n('Someone mentioned you in video comments'), |
46 | newInstanceFollower: this.i18n('Your instance has a new follower') | 46 | newInstanceFollower: this.i18n('Your instance has a new follower'), |
47 | autoInstanceFollowing: this.i18n('Your instance auto followed another instance') | ||
47 | } | 48 | } |
48 | this.notificationSettingKeys = Object.keys(this.labelNotifications) as (keyof UserNotificationSetting)[] | 49 | this.notificationSettingKeys = Object.keys(this.labelNotifications) as (keyof UserNotificationSetting)[] |
49 | 50 | ||
@@ -51,7 +52,8 @@ export class MyAccountNotificationPreferencesComponent implements OnInit { | |||
51 | videoAbuseAsModerator: UserRight.MANAGE_VIDEO_ABUSES, | 52 | videoAbuseAsModerator: UserRight.MANAGE_VIDEO_ABUSES, |
52 | videoAutoBlacklistAsModerator: UserRight.MANAGE_VIDEO_BLACKLIST, | 53 | videoAutoBlacklistAsModerator: UserRight.MANAGE_VIDEO_BLACKLIST, |
53 | newUserRegistration: UserRight.MANAGE_USERS, | 54 | newUserRegistration: UserRight.MANAGE_USERS, |
54 | newInstanceFollower: UserRight.MANAGE_SERVER_FOLLOW | 55 | newInstanceFollower: UserRight.MANAGE_SERVER_FOLLOW, |
56 | autoInstanceFollowing: UserRight.MANAGE_CONFIGURATION | ||
55 | } | 57 | } |
56 | 58 | ||
57 | this.emailEnabled = this.serverService.getConfig().email.enabled | 59 | this.emailEnabled = this.serverService.getConfig().email.enabled |
diff --git a/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.html b/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.html index 2796dd2db..a11238925 100644 --- a/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.html +++ b/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.html | |||
@@ -1,10 +1,13 @@ | |||
1 | <form role="form" (ngSubmit)="updateDetails()" [formGroup]="form"> | 1 | <form role="form" (ngSubmit)="updateDetails()" [formGroup]="form"> |
2 | <div class="form-group"> | 2 | <div class="form-group"> |
3 | <label i18n for="nsfwPolicy">Default policy on videos containing sensitive content</label> | 3 | <label i18n for="nsfwPolicy">Default policy on videos containing sensitive content</label> |
4 | <my-help | 4 | <my-help> |
5 | helpType="custom" i18n-customHtml | 5 | <ng-template ptTemplate="customHtml"> |
6 | customHtml="With <strong>Do not list</strong> or <strong>Blur thumbnails</strong>, a confirmation will be requested to watch the video." | 6 | <ng-container i18n> |
7 | ></my-help> | 7 | With <strong>Do not list</strong> or <strong>Blur thumbnails</strong>, a confirmation will be requested to watch the video. |
8 | </ng-container> | ||
9 | </ng-template> | ||
10 | </my-help> | ||
8 | 11 | ||
9 | <div class="peertube-select-container"> | 12 | <div class="peertube-select-container"> |
10 | <select id="nsfwPolicy" formControlName="nsfwPolicy"> | 13 | <select id="nsfwPolicy" formControlName="nsfwPolicy"> |
@@ -17,13 +20,15 @@ | |||
17 | 20 | ||
18 | <div class="form-group"> | 21 | <div class="form-group"> |
19 | <label i18n for="videoLanguages">Only display videos in the following languages</label> | 22 | <label i18n for="videoLanguages">Only display videos in the following languages</label> |
20 | <my-help i18n-customHtml | 23 | <my-help> |
21 | customHtml="In Recently added, Trending, Local and Search pages" | 24 | <ng-template ptTemplate="customHtml"> |
22 | ></my-help> | 25 | <ng-container i18n>In Recently added, Trending, Local and Search pages</ng-container> |
26 | </ng-template> | ||
27 | </my-help> | ||
23 | 28 | ||
24 | <div> | 29 | <div> |
25 | <p-multiSelect | 30 | <p-multiSelect |
26 | [options]="languageItems" formControlName="videoLanguages" showToggleAll="true" | 31 | inputId="videoLanguages" [options]="languageItems" formControlName="videoLanguages" showToggleAll="true" |
27 | [defaultLabel]="getDefaultVideoLanguageLabel()" [selectedItemsLabel]="getSelectedVideoLanguageLabel()" | 32 | [defaultLabel]="getDefaultVideoLanguageLabel()" [selectedItemsLabel]="getSelectedVideoLanguageLabel()" |
28 | emptyFilterMessage="No results found" i18n-emptyFilterMessage | 33 | emptyFilterMessage="No results found" i18n-emptyFilterMessage |
29 | ></p-multiSelect> | 34 | ></p-multiSelect> |
diff --git a/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.ts b/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.ts index 77febf179..4fb828082 100644 --- a/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.ts +++ b/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.ts | |||
@@ -5,9 +5,9 @@ import { AuthService } from '../../../core' | |||
5 | import { FormReactive, User, UserService } from '../../../shared' | 5 | import { FormReactive, User, UserService } from '../../../shared' |
6 | import { I18n } from '@ngx-translate/i18n-polyfill' | 6 | import { I18n } from '@ngx-translate/i18n-polyfill' |
7 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' | 7 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' |
8 | import { Subject } from 'rxjs' | 8 | import { forkJoin, Subject } from 'rxjs' |
9 | import { SelectItem } from 'primeng/api' | 9 | import { SelectItem } from 'primeng/api' |
10 | import { switchMap } from 'rxjs/operators' | 10 | import { first } from 'rxjs/operators' |
11 | 11 | ||
12 | @Component({ | 12 | @Component({ |
13 | selector: 'my-account-video-settings', | 13 | selector: 'my-account-video-settings', |
@@ -39,30 +39,31 @@ export class MyAccountVideoSettingsComponent extends FormReactive implements OnI | |||
39 | videoLanguages: null | 39 | videoLanguages: null |
40 | }) | 40 | }) |
41 | 41 | ||
42 | this.serverService.videoLanguagesLoaded | 42 | forkJoin([ |
43 | .pipe(switchMap(() => this.userInformationLoaded)) | 43 | this.serverService.videoLanguagesLoaded.pipe(first()), |
44 | .subscribe(() => { | 44 | this.userInformationLoaded.pipe(first()) |
45 | const languages = this.serverService.getVideoLanguages() | 45 | ]).subscribe(() => { |
46 | 46 | const languages = this.serverService.getVideoLanguages() | |
47 | this.languageItems = [ { label: this.i18n('Unknown language'), value: '_unknown' } ] | 47 | |
48 | this.languageItems = this.languageItems | 48 | this.languageItems = [ { label: this.i18n('Unknown language'), value: '_unknown' } ] |
49 | .concat(languages.map(l => ({ label: l.label, value: l.id }))) | 49 | this.languageItems = this.languageItems |
50 | 50 | .concat(languages.map(l => ({ label: l.label, value: l.id }))) | |
51 | const videoLanguages = this.user.videoLanguages | 51 | |
52 | ? this.user.videoLanguages | 52 | const videoLanguages = this.user.videoLanguages |
53 | : this.languageItems.map(l => l.value) | 53 | ? this.user.videoLanguages |
54 | 54 | : this.languageItems.map(l => l.value) | |
55 | this.form.patchValue({ | 55 | |
56 | nsfwPolicy: this.user.nsfwPolicy, | 56 | this.form.patchValue({ |
57 | webTorrentEnabled: this.user.webTorrentEnabled, | 57 | nsfwPolicy: this.user.nsfwPolicy, |
58 | autoPlayVideo: this.user.autoPlayVideo === true, | 58 | webTorrentEnabled: this.user.webTorrentEnabled, |
59 | videoLanguages | 59 | autoPlayVideo: this.user.autoPlayVideo === true, |
60 | }) | 60 | videoLanguages |
61 | }) | 61 | }) |
62 | }) | ||
62 | } | 63 | } |
63 | 64 | ||
64 | updateDetails () { | 65 | updateDetails () { |
65 | const nsfwPolicy = this.form.value['nsfwPolicy'] | 66 | const nsfwPolicy = this.form.value[ 'nsfwPolicy' ] |
66 | const webTorrentEnabled = this.form.value['webTorrentEnabled'] | 67 | const webTorrentEnabled = this.form.value['webTorrentEnabled'] |
67 | const autoPlayVideo = this.form.value['autoPlayVideo'] | 68 | const autoPlayVideo = this.form.value['autoPlayVideo'] |
68 | 69 | ||
diff --git a/client/src/app/+my-account/my-account.module.ts b/client/src/app/+my-account/my-account.module.ts index 571f46de9..6cf1499d3 100644 --- a/client/src/app/+my-account/my-account.module.ts +++ b/client/src/app/+my-account/my-account.module.ts | |||
@@ -37,7 +37,6 @@ import { | |||
37 | } from '@app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component' | 37 | } from '@app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component' |
38 | import { DragDropModule } from '@angular/cdk/drag-drop' | 38 | import { DragDropModule } from '@angular/cdk/drag-drop' |
39 | import { MyAccountChangeEmailComponent } from '@app/+my-account/my-account-settings/my-account-change-email' | 39 | import { MyAccountChangeEmailComponent } from '@app/+my-account/my-account-settings/my-account-change-email' |
40 | import { MultiSelectModule } from 'primeng/multiselect' | ||
41 | import { MyAccountInterfaceSettingsComponent } from '@app/+my-account/my-account-settings/my-account-interface' | 40 | import { MyAccountInterfaceSettingsComponent } from '@app/+my-account/my-account-settings/my-account-interface' |
42 | 41 | ||
43 | @NgModule({ | 42 | @NgModule({ |
@@ -48,8 +47,7 @@ import { MyAccountInterfaceSettingsComponent } from '@app/+my-account/my-account | |||
48 | SharedModule, | 47 | SharedModule, |
49 | TableModule, | 48 | TableModule, |
50 | InputSwitchModule, | 49 | InputSwitchModule, |
51 | DragDropModule, | 50 | DragDropModule |
52 | MultiSelectModule | ||
53 | ], | 51 | ], |
54 | 52 | ||
55 | declarations: [ | 53 | declarations: [ |
diff --git a/client/src/app/+signup/+register/register-step-user.component.html b/client/src/app/+signup/+register/register-step-user.component.html index 47b3be8cc..4381702ae 100644 --- a/client/src/app/+signup/+register/register-step-user.component.html +++ b/client/src/app/+signup/+register/register-step-user.component.html | |||
@@ -60,11 +60,16 @@ | |||
60 | </div> | 60 | </div> |
61 | 61 | ||
62 | <div class="form-group form-group-terms"> | 62 | <div class="form-group form-group-terms"> |
63 | <my-peertube-checkbox | 63 | <my-peertube-checkbox inputName="terms" formControlName="terms"> |
64 | inputName="terms" formControlName="terms" | 64 | <ng-template ptTemplate="label"> |
65 | i18n-labelHtml | 65 | <ng-container i18n> |
66 | labelHtml="I am at least 16 years old and agree to the <a href='/about/instance#terms-section' target='_blank'rel='noopener noreferrer'>Terms</a> of this instance" | 66 | I am at least 16 years old and agree |
67 | ></my-peertube-checkbox> | 67 | to the <a (click)="onTermsClick($event)" href='#'>Terms</a> |
68 | <ng-container *ngIf="hasCodeOfConduct"> and to the <a (click)="onCodeOfConductClick($event)" href='#'>Code of Conduct</a></ng-container> | ||
69 | of this instance | ||
70 | </ng-container> | ||
71 | </ng-template> | ||
72 | </my-peertube-checkbox> | ||
68 | 73 | ||
69 | <div *ngIf="formErrors.terms" class="form-error"> | 74 | <div *ngIf="formErrors.terms" class="form-error"> |
70 | {{ formErrors.terms }} | 75 | {{ formErrors.terms }} |
diff --git a/client/src/app/+signup/+register/register-step-user.component.ts b/client/src/app/+signup/+register/register-step-user.component.ts index 3b71fd3c4..6c96f20b4 100644 --- a/client/src/app/+signup/+register/register-step-user.component.ts +++ b/client/src/app/+signup/+register/register-step-user.component.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | import { Component, EventEmitter, OnInit, Output } from '@angular/core' | 1 | import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core' |
2 | import { AuthService } from '@app/core' | 2 | import { AuthService } from '@app/core' |
3 | import { FormReactive, UserService, UserValidatorsService } from '@app/shared' | 3 | import { FormReactive, UserService, UserValidatorsService } from '@app/shared' |
4 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' | 4 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' |
@@ -12,7 +12,11 @@ import { concat, of } from 'rxjs' | |||
12 | styleUrls: [ './register.component.scss' ] | 12 | styleUrls: [ './register.component.scss' ] |
13 | }) | 13 | }) |
14 | export class RegisterStepUserComponent extends FormReactive implements OnInit { | 14 | export class RegisterStepUserComponent extends FormReactive implements OnInit { |
15 | @Input() hasCodeOfConduct = false | ||
16 | |||
15 | @Output() formBuilt = new EventEmitter<FormGroup>() | 17 | @Output() formBuilt = new EventEmitter<FormGroup>() |
18 | @Output() termsClick = new EventEmitter<void>() | ||
19 | @Output() codeOfConductClick = new EventEmitter<void>() | ||
16 | 20 | ||
17 | constructor ( | 21 | constructor ( |
18 | protected formValidatorService: FormValidatorService, | 22 | protected formValidatorService: FormValidatorService, |
@@ -45,6 +49,16 @@ export class RegisterStepUserComponent extends FormReactive implements OnInit { | |||
45 | .subscribe(([ oldValue, newValue ]) => this.onDisplayNameChange(oldValue, newValue)) | 49 | .subscribe(([ oldValue, newValue ]) => this.onDisplayNameChange(oldValue, newValue)) |
46 | } | 50 | } |
47 | 51 | ||
52 | onTermsClick (event: Event) { | ||
53 | event.preventDefault() | ||
54 | this.termsClick.emit() | ||
55 | } | ||
56 | |||
57 | onCodeOfConductClick (event: Event) { | ||
58 | event.preventDefault() | ||
59 | this.codeOfConductClick.emit() | ||
60 | } | ||
61 | |||
48 | private onDisplayNameChange (oldDisplayName: string, newDisplayName: string) { | 62 | private onDisplayNameChange (oldDisplayName: string, newDisplayName: string) { |
49 | const username = this.form.value['username'] || '' | 63 | const username = this.form.value['username'] || '' |
50 | 64 | ||
diff --git a/client/src/app/+signup/+register/register.component.html b/client/src/app/+signup/+register/register.component.html index e4d647fef..906e29aed 100644 --- a/client/src/app/+signup/+register/register.component.html +++ b/client/src/app/+signup/+register/register.component.html | |||
@@ -7,11 +7,15 @@ | |||
7 | <my-signup-success *ngIf="signupDone" [message]="success"></my-signup-success> | 7 | <my-signup-success *ngIf="signupDone" [message]="success"></my-signup-success> |
8 | <div *ngIf="info" class="alert alert-info">{{ info }}</div> | 8 | <div *ngIf="info" class="alert alert-info">{{ info }}</div> |
9 | 9 | ||
10 | <div class="wrapper" *ngIf="!signupDone"> | 10 | <div class="wrapper" [hidden]="signupDone"> |
11 | <div> | 11 | <div class="register-form"> |
12 | <my-custom-stepper linear *ngIf="!signupDone"> | 12 | <my-custom-stepper linear *ngIf="!signupDone"> |
13 | <cdk-step [stepControl]="formStepUser" i18n-label label="User"> | 13 | <cdk-step [stepControl]="formStepUser" i18n-label label="User"> |
14 | <my-register-step-user (formBuilt)="onUserFormBuilt($event)"></my-register-step-user> | 14 | <my-register-step-user |
15 | [hasCodeOfConduct]="!!aboutHtml.codeOfConduct" | ||
16 | (formBuilt)="onUserFormBuilt($event)" (termsClick)="onTermsClick()" (codeOfConductClick)="onCodeOfConductClick()" | ||
17 | > | ||
18 | </my-register-step-user> | ||
15 | 19 | ||
16 | <button i18n cdkStepperNext [disabled]="!formStepUser || !formStepUser.valid">Next</button> | 20 | <button i18n cdkStepperNext [disabled]="!formStepUser || !formStepUser.valid">Next</button> |
17 | </cdk-step> | 21 | </cdk-step> |
@@ -38,9 +42,56 @@ | |||
38 | </my-custom-stepper> | 42 | </my-custom-stepper> |
39 | </div> | 43 | </div> |
40 | 44 | ||
41 | <div> | 45 | <div class="instance-information"> |
42 | <label i18n>Features found on this instance</label> | 46 | <ngb-accordion [closeOthers]="true" #accordion="ngbAccordion"> |
43 | <my-instance-features-table></my-instance-features-table> | 47 | <ngb-panel id="instance-features" i18n-title title="Features found on this instance"> |
48 | <ng-template ngbPanelContent> | ||
49 | <my-instance-features-table></my-instance-features-table> | ||
50 | </ng-template> | ||
51 | </ngb-panel> | ||
52 | |||
53 | <ng-container *ngIf="about"> | ||
54 | <ngb-panel | ||
55 | *ngIf="aboutHtml.administrator || about.instance.maintenanceLifetime || about.instance.businessModel" | ||
56 | id="admin-sustainability" i18n-title title="Administrators & Sustainability" | ||
57 | > | ||
58 | <ng-template ngbPanelContent> | ||
59 | <div class="block"> | ||
60 | <strong i18n>Who are we?</strong> | ||
61 | <div [innerHTML]="aboutHtml.administrator"></div> | ||
62 | </div> | ||
63 | |||
64 | <div class="block"> | ||
65 | <strong i18n>How long do we plan to maintain this instance?</strong> | ||
66 | <div [innerHTML]="about.instance.maintenanceLifetime"></div> | ||
67 | </div> | ||
68 | |||
69 | <div class="block"> | ||
70 | <strong i18n>How will we pay this instance?</strong> | ||
71 | <div [innerHTML]="about.instance.businessModel"></div> | ||
72 | </div> | ||
73 | </ng-template> | ||
74 | </ngb-panel> | ||
75 | |||
76 | <ngb-panel *ngIf="aboutHtml.moderationInformation" id="moderation-information" i18n-title title="Moderation information"> | ||
77 | <ng-template ngbPanelContent> | ||
78 | <div class="block" [innerHTML]="aboutHtml.moderationInformation"></div> | ||
79 | </ng-template> | ||
80 | </ngb-panel> | ||
81 | |||
82 | <ngb-panel *ngIf="aboutHtml.codeOfConduct" id="code-of-conduct" i18n-title title="Code of conduct"> | ||
83 | <ng-template ngbPanelContent> | ||
84 | <div class="block" [innerHTML]="aboutHtml.codeOfConduct"></div> | ||
85 | </ng-template> | ||
86 | </ngb-panel> | ||
87 | |||
88 | <ngb-panel *ngIf="aboutHtml.terms" id="terms" i18n-title title="Terms"> | ||
89 | <ng-template ngbPanelContent> | ||
90 | <div class="block" [innerHTML]="aboutHtml.terms"></div> | ||
91 | </ng-template> | ||
92 | </ngb-panel> | ||
93 | </ng-container> | ||
94 | </ngb-accordion> | ||
44 | </div> | 95 | </div> |
45 | </div> | 96 | </div> |
46 | 97 | ||
diff --git a/client/src/app/+signup/+register/register.component.scss b/client/src/app/+signup/+register/register.component.scss index 9405b5293..2f62dd59d 100644 --- a/client/src/app/+signup/+register/register.component.scss +++ b/client/src/app/+signup/+register/register.component.scss | |||
@@ -1,5 +1,9 @@ | |||
1 | @import '_variables'; | 1 | @import '_variables'; |
2 | @import '_mixins'; | 2 | @import '_mixins'; |
3 | @import "./_bootstrap-variables"; | ||
4 | |||
5 | @import '~bootstrap/scss/functions'; | ||
6 | @import '~bootstrap/scss/variables'; | ||
3 | 7 | ||
4 | .alert { | 8 | .alert { |
5 | font-size: 15px; | 9 | font-size: 15px; |
@@ -13,7 +17,32 @@ | |||
13 | 17 | ||
14 | & > div { | 18 | & > div { |
15 | margin-bottom: 40px; | 19 | margin-bottom: 40px; |
16 | width: 450px; | 20 | |
21 | &.register-form { | ||
22 | width: 450px; | ||
23 | } | ||
24 | |||
25 | &.instance-information { | ||
26 | width: 600px; | ||
27 | margin-bottom: 40px; | ||
28 | |||
29 | .block { | ||
30 | font-size: 15px; | ||
31 | margin-bottom: 15px; | ||
32 | padding: 0 $btn-padding-x; | ||
33 | } | ||
34 | |||
35 | @media screen and (max-width: 1500px) { | ||
36 | width: 450px; | ||
37 | } | ||
38 | |||
39 | ngb-accordion ::ng-deep { | ||
40 | .btn { | ||
41 | font-weight: $font-semibold !important; | ||
42 | color: var(--mainForegroundColor) !important; | ||
43 | } | ||
44 | } | ||
45 | } | ||
17 | 46 | ||
18 | @media screen and (max-width: 500px) { | 47 | @media screen and (max-width: 500px) { |
19 | width: auto; | 48 | width: auto; |
@@ -21,12 +50,6 @@ | |||
21 | } | 50 | } |
22 | } | 51 | } |
23 | 52 | ||
24 | my-instance-features-table { | ||
25 | display: block; | ||
26 | |||
27 | margin-bottom: 40px; | ||
28 | } | ||
29 | |||
30 | .form-group-terms { | 53 | .form-group-terms { |
31 | margin: 30px 0; | 54 | margin: 30px 0; |
32 | } | 55 | } |
diff --git a/client/src/app/+signup/+register/register.component.ts b/client/src/app/+signup/+register/register.component.ts index cd6059728..d470ef4dc 100644 --- a/client/src/app/+signup/+register/register.component.ts +++ b/client/src/app/+signup/+register/register.component.ts | |||
@@ -1,21 +1,35 @@ | |||
1 | import { Component } from '@angular/core' | 1 | import { Component, OnInit, ViewChild } from '@angular/core' |
2 | import { AuthService, Notifier, RedirectService, ServerService } from '@app/core' | 2 | import { AuthService, Notifier, RedirectService, ServerService } from '@app/core' |
3 | import { UserService, UserValidatorsService } from '@app/shared' | 3 | import { UserService, UserValidatorsService } from '@app/shared' |
4 | import { I18n } from '@ngx-translate/i18n-polyfill' | 4 | import { I18n } from '@ngx-translate/i18n-polyfill' |
5 | import { UserRegister } from '@shared/models/users/user-register.model' | 5 | import { UserRegister } from '@shared/models/users/user-register.model' |
6 | import { FormGroup } from '@angular/forms' | 6 | import { FormGroup } from '@angular/forms' |
7 | import { About } from '@shared/models/server' | ||
8 | import { InstanceService } from '@app/shared/instance/instance.service' | ||
9 | import { NgbAccordion } from '@ng-bootstrap/ng-bootstrap' | ||
7 | 10 | ||
8 | @Component({ | 11 | @Component({ |
9 | selector: 'my-register', | 12 | selector: 'my-register', |
10 | templateUrl: './register.component.html', | 13 | templateUrl: './register.component.html', |
11 | styleUrls: [ './register.component.scss' ] | 14 | styleUrls: [ './register.component.scss' ] |
12 | }) | 15 | }) |
13 | export class RegisterComponent { | 16 | export class RegisterComponent implements OnInit { |
17 | @ViewChild('accordion', { static: true }) accordion: NgbAccordion | ||
18 | |||
14 | info: string = null | 19 | info: string = null |
15 | error: string = null | 20 | error: string = null |
16 | success: string = null | 21 | success: string = null |
17 | signupDone = false | 22 | signupDone = false |
18 | 23 | ||
24 | about: About | ||
25 | aboutHtml = { | ||
26 | description: '', | ||
27 | terms: '', | ||
28 | codeOfConduct: '', | ||
29 | moderationInformation: '', | ||
30 | administrator: '' | ||
31 | } | ||
32 | |||
19 | formStepUser: FormGroup | 33 | formStepUser: FormGroup |
20 | formStepChannel: FormGroup | 34 | formStepChannel: FormGroup |
21 | 35 | ||
@@ -26,6 +40,7 @@ export class RegisterComponent { | |||
26 | private userService: UserService, | 40 | private userService: UserService, |
27 | private serverService: ServerService, | 41 | private serverService: ServerService, |
28 | private redirectService: RedirectService, | 42 | private redirectService: RedirectService, |
43 | private instanceService: InstanceService, | ||
29 | private i18n: I18n | 44 | private i18n: I18n |
30 | ) { | 45 | ) { |
31 | } | 46 | } |
@@ -34,6 +49,19 @@ export class RegisterComponent { | |||
34 | return this.serverService.getConfig().signup.requiresEmailVerification | 49 | return this.serverService.getConfig().signup.requiresEmailVerification |
35 | } | 50 | } |
36 | 51 | ||
52 | ngOnInit (): void { | ||
53 | this.instanceService.getAbout() | ||
54 | .subscribe( | ||
55 | async about => { | ||
56 | this.about = about | ||
57 | |||
58 | this.aboutHtml = await this.instanceService.buildHtml(about) | ||
59 | }, | ||
60 | |||
61 | err => this.notifier.error(err.message) | ||
62 | ) | ||
63 | } | ||
64 | |||
37 | hasSameChannelAndAccountNames () { | 65 | hasSameChannelAndAccountNames () { |
38 | return this.getUsername() === this.getChannelName() | 66 | return this.getUsername() === this.getChannelName() |
39 | } | 67 | } |
@@ -58,6 +86,14 @@ export class RegisterComponent { | |||
58 | this.formStepChannel = form | 86 | this.formStepChannel = form |
59 | } | 87 | } |
60 | 88 | ||
89 | onTermsClick () { | ||
90 | if (this.accordion) this.accordion.toggle('terms') | ||
91 | } | ||
92 | |||
93 | onCodeOfConductClick () { | ||
94 | if (this.accordion) this.accordion.toggle('code-of-conduct') | ||
95 | } | ||
96 | |||
61 | signup () { | 97 | signup () { |
62 | this.error = null | 98 | this.error = null |
63 | 99 | ||
diff --git a/client/src/app/+signup/+register/register.module.ts b/client/src/app/+signup/+register/register.module.ts index 46336cbd0..e55f83990 100644 --- a/client/src/app/+signup/+register/register.module.ts +++ b/client/src/app/+signup/+register/register.module.ts | |||
@@ -7,13 +7,15 @@ import { RegisterStepChannelComponent } from './register-step-channel.component' | |||
7 | import { RegisterStepUserComponent } from './register-step-user.component' | 7 | import { RegisterStepUserComponent } from './register-step-user.component' |
8 | import { CustomStepperComponent } from './custom-stepper.component' | 8 | import { CustomStepperComponent } from './custom-stepper.component' |
9 | import { SignupSharedModule } from '@app/+signup/shared/signup-shared.module' | 9 | import { SignupSharedModule } from '@app/+signup/shared/signup-shared.module' |
10 | import { NgbAccordionModule } from '@ng-bootstrap/ng-bootstrap' | ||
10 | 11 | ||
11 | @NgModule({ | 12 | @NgModule({ |
12 | imports: [ | 13 | imports: [ |
13 | RegisterRoutingModule, | 14 | RegisterRoutingModule, |
14 | SharedModule, | 15 | SharedModule, |
15 | CdkStepperModule, | 16 | CdkStepperModule, |
16 | SignupSharedModule | 17 | SignupSharedModule, |
18 | NgbAccordionModule | ||
17 | ], | 19 | ], |
18 | 20 | ||
19 | declarations: [ | 21 | declarations: [ |
diff --git a/client/src/app/app.component.html b/client/src/app/app.component.html index 07a576083..81b4351c5 100644 --- a/client/src/app/app.component.html +++ b/client/src/app/app.component.html | |||
@@ -54,3 +54,8 @@ | |||
54 | </div> | 54 | </div> |
55 | </ng-template> | 55 | </ng-template> |
56 | </p-toast> | 56 | </p-toast> |
57 | |||
58 | <ng-template [ngIf]="isUserLoggedIn()"> | ||
59 | <my-welcome-modal #welcomeModal></my-welcome-modal> | ||
60 | <my-instance-config-warning-modal #instanceConfigWarningModal></my-instance-config-warning-modal> | ||
61 | </ng-template> | ||
diff --git a/client/src/app/app.component.ts b/client/src/app/app.component.ts index 64bfb9671..6b18e5feb 100644 --- a/client/src/app/app.component.ts +++ b/client/src/app/app.component.ts | |||
@@ -1,10 +1,10 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | 1 | import { Component, OnInit, ViewChild } from '@angular/core' |
2 | import { DomSanitizer, SafeHtml } from '@angular/platform-browser' | 2 | import { DomSanitizer, SafeHtml } from '@angular/platform-browser' |
3 | import { Event, GuardsCheckStart, NavigationEnd, Router, Scroll } from '@angular/router' | 3 | import { Event, GuardsCheckStart, NavigationEnd, Router, Scroll } from '@angular/router' |
4 | import { AuthService, RedirectService, ServerService, ThemeService } from '@app/core' | 4 | import { AuthService, RedirectService, ServerService, ThemeService } from '@app/core' |
5 | import { is18nPath } from '../../../shared/models/i18n' | 5 | import { is18nPath } from '../../../shared/models/i18n' |
6 | import { ScreenService } from '@app/shared/misc/screen.service' | 6 | import { ScreenService } from '@app/shared/misc/screen.service' |
7 | import { debounceTime, filter, map, pairwise, skip } from 'rxjs/operators' | 7 | import { debounceTime, filter, map, pairwise, skip, switchMap } from 'rxjs/operators' |
8 | import { Hotkey, HotkeysService } from 'angular2-hotkeys' | 8 | import { Hotkey, HotkeysService } from 'angular2-hotkeys' |
9 | import { I18n } from '@ngx-translate/i18n-polyfill' | 9 | import { I18n } from '@ngx-translate/i18n-polyfill' |
10 | import { fromEvent } from 'rxjs' | 10 | import { fromEvent } from 'rxjs' |
@@ -13,6 +13,11 @@ import { PluginService } from '@app/core/plugins/plugin.service' | |||
13 | import { HooksService } from '@app/core/plugins/hooks.service' | 13 | import { HooksService } from '@app/core/plugins/hooks.service' |
14 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | 14 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' |
15 | import { POP_STATE_MODAL_DISMISS } from '@app/shared/misc/constants' | 15 | import { POP_STATE_MODAL_DISMISS } from '@app/shared/misc/constants' |
16 | import { WelcomeModalComponent } from '@app/modal/welcome-modal.component' | ||
17 | import { InstanceConfigWarningModalComponent } from '@app/modal/instance-config-warning-modal.component' | ||
18 | import { UserRole } from '@shared/models' | ||
19 | import { User } from '@app/shared' | ||
20 | import { InstanceService } from '@app/shared/instance/instance.service' | ||
16 | 21 | ||
17 | @Component({ | 22 | @Component({ |
18 | selector: 'my-app', | 23 | selector: 'my-app', |
@@ -20,6 +25,9 @@ import { POP_STATE_MODAL_DISMISS } from '@app/shared/misc/constants' | |||
20 | styleUrls: [ './app.component.scss' ] | 25 | styleUrls: [ './app.component.scss' ] |
21 | }) | 26 | }) |
22 | export class AppComponent implements OnInit { | 27 | export class AppComponent implements OnInit { |
28 | @ViewChild('welcomeModal', { static: false }) welcomeModal: WelcomeModalComponent | ||
29 | @ViewChild('instanceConfigWarningModal', { static: false }) instanceConfigWarningModal: InstanceConfigWarningModalComponent | ||
30 | |||
23 | isMenuDisplayed = true | 31 | isMenuDisplayed = true |
24 | isMenuChangedByUser = false | 32 | isMenuChangedByUser = false |
25 | 33 | ||
@@ -32,6 +40,7 @@ export class AppComponent implements OnInit { | |||
32 | private authService: AuthService, | 40 | private authService: AuthService, |
33 | private serverService: ServerService, | 41 | private serverService: ServerService, |
34 | private pluginService: PluginService, | 42 | private pluginService: PluginService, |
43 | private instanceService: InstanceService, | ||
35 | private domSanitizer: DomSanitizer, | 44 | private domSanitizer: DomSanitizer, |
36 | private redirectService: RedirectService, | 45 | private redirectService: RedirectService, |
37 | private screenService: ScreenService, | 46 | private screenService: ScreenService, |
@@ -96,6 +105,8 @@ export class AppComponent implements OnInit { | |||
96 | .subscribe(() => this.onResize()) | 105 | .subscribe(() => this.onResize()) |
97 | 106 | ||
98 | this.location.onPopState(() => this.modalService.dismissAll(POP_STATE_MODAL_DISMISS)) | 107 | this.location.onPopState(() => this.modalService.dismissAll(POP_STATE_MODAL_DISMISS)) |
108 | |||
109 | this.openModalsIfNeeded() | ||
99 | } | 110 | } |
100 | 111 | ||
101 | isUserLoggedIn () { | 112 | isUserLoggedIn () { |
@@ -220,32 +231,66 @@ export class AppComponent implements OnInit { | |||
220 | this.hooks.runAction('action:application.init', 'common') | 231 | this.hooks.runAction('action:application.init', 'common') |
221 | } | 232 | } |
222 | 233 | ||
234 | private async openModalsIfNeeded () { | ||
235 | this.serverService.configLoaded | ||
236 | .pipe( | ||
237 | switchMap(() => this.authService.userInformationLoaded), | ||
238 | map(() => this.authService.getUser()), | ||
239 | filter(user => user.role === UserRole.ADMINISTRATOR) | ||
240 | ).subscribe(user => setTimeout(() => this.openAdminModals(user))) // setTimeout because of ngIf in template | ||
241 | } | ||
242 | |||
243 | private async openAdminModals (user: User) { | ||
244 | if (user.noWelcomeModal !== true) return this.welcomeModal.show() | ||
245 | |||
246 | const config = this.serverService.getConfig() | ||
247 | if (user.noInstanceConfigWarningModal === true || !config.signup.allowed) return | ||
248 | |||
249 | this.instanceService.getAbout() | ||
250 | .subscribe(about => { | ||
251 | if ( | ||
252 | config.instance.name.toLowerCase() === 'peertube' || | ||
253 | !about.instance.terms || | ||
254 | !about.instance.administrator || | ||
255 | !about.instance.maintenanceLifetime | ||
256 | ) { | ||
257 | this.instanceConfigWarningModal.show(about) | ||
258 | } | ||
259 | }) | ||
260 | } | ||
261 | |||
223 | private initHotkeys () { | 262 | private initHotkeys () { |
224 | this.hotkeysService.add([ | 263 | this.hotkeysService.add([ |
225 | new Hotkey(['/', 's'], (event: KeyboardEvent): boolean => { | 264 | new Hotkey(['/', 's'], (event: KeyboardEvent): boolean => { |
226 | document.getElementById('search-video').focus() | 265 | document.getElementById('search-video').focus() |
227 | return false | 266 | return false |
228 | }, undefined, this.i18n('Focus the search bar')), | 267 | }, undefined, this.i18n('Focus the search bar')), |
268 | |||
229 | new Hotkey('b', (event: KeyboardEvent): boolean => { | 269 | new Hotkey('b', (event: KeyboardEvent): boolean => { |
230 | this.toggleMenu() | 270 | this.toggleMenu() |
231 | return false | 271 | return false |
232 | }, undefined, this.i18n('Toggle the left menu')), | 272 | }, undefined, this.i18n('Toggle the left menu')), |
273 | |||
233 | new Hotkey('g o', (event: KeyboardEvent): boolean => { | 274 | new Hotkey('g o', (event: KeyboardEvent): boolean => { |
234 | this.router.navigate([ '/videos/overview' ]) | 275 | this.router.navigate([ '/videos/overview' ]) |
235 | return false | 276 | return false |
236 | }, undefined, this.i18n('Go to the discover videos page')), | 277 | }, undefined, this.i18n('Go to the discover videos page')), |
278 | |||
237 | new Hotkey('g t', (event: KeyboardEvent): boolean => { | 279 | new Hotkey('g t', (event: KeyboardEvent): boolean => { |
238 | this.router.navigate([ '/videos/trending' ]) | 280 | this.router.navigate([ '/videos/trending' ]) |
239 | return false | 281 | return false |
240 | }, undefined, this.i18n('Go to the trending videos page')), | 282 | }, undefined, this.i18n('Go to the trending videos page')), |
283 | |||
241 | new Hotkey('g r', (event: KeyboardEvent): boolean => { | 284 | new Hotkey('g r', (event: KeyboardEvent): boolean => { |
242 | this.router.navigate([ '/videos/recently-added' ]) | 285 | this.router.navigate([ '/videos/recently-added' ]) |
243 | return false | 286 | return false |
244 | }, undefined, this.i18n('Go to the recently added videos page')), | 287 | }, undefined, this.i18n('Go to the recently added videos page')), |
288 | |||
245 | new Hotkey('g l', (event: KeyboardEvent): boolean => { | 289 | new Hotkey('g l', (event: KeyboardEvent): boolean => { |
246 | this.router.navigate([ '/videos/local' ]) | 290 | this.router.navigate([ '/videos/local' ]) |
247 | return false | 291 | return false |
248 | }, undefined, this.i18n('Go to the local videos page')), | 292 | }, undefined, this.i18n('Go to the local videos page')), |
293 | |||
249 | new Hotkey('g u', (event: KeyboardEvent): boolean => { | 294 | new Hotkey('g u', (event: KeyboardEvent): boolean => { |
250 | this.router.navigate([ '/videos/upload' ]) | 295 | this.router.navigate([ '/videos/upload' ]) |
251 | return false | 296 | return false |
diff --git a/client/src/app/app.module.ts b/client/src/app/app.module.ts index 1e2936a37..a3ea33ca9 100644 --- a/client/src/app/app.module.ts +++ b/client/src/app/app.module.ts | |||
@@ -18,6 +18,8 @@ import { VideosModule } from './videos' | |||
18 | import { buildFileLocale, getCompleteLocale, isDefaultLocale } from '../../../shared/models/i18n' | 18 | import { buildFileLocale, getCompleteLocale, isDefaultLocale } from '../../../shared/models/i18n' |
19 | import { getDevLocale, isOnDevLocale } from '@app/shared/i18n/i18n-utils' | 19 | import { getDevLocale, isOnDevLocale } from '@app/shared/i18n/i18n-utils' |
20 | import { SearchModule } from '@app/search' | 20 | import { SearchModule } from '@app/search' |
21 | import { WelcomeModalComponent } from '@app/modal/welcome-modal.component' | ||
22 | import { InstanceConfigWarningModalComponent } from '@app/modal/instance-config-warning-modal.component' | ||
21 | 23 | ||
22 | export function metaFactory (serverService: ServerService): MetaLoader { | 24 | export function metaFactory (serverService: ServerService): MetaLoader { |
23 | return new MetaStaticLoader({ | 25 | return new MetaStaticLoader({ |
@@ -39,7 +41,10 @@ export function metaFactory (serverService: ServerService): MetaLoader { | |||
39 | MenuComponent, | 41 | MenuComponent, |
40 | LanguageChooserComponent, | 42 | LanguageChooserComponent, |
41 | AvatarNotificationComponent, | 43 | AvatarNotificationComponent, |
42 | HeaderComponent | 44 | HeaderComponent, |
45 | |||
46 | WelcomeModalComponent, | ||
47 | InstanceConfigWarningModalComponent | ||
43 | ], | 48 | ], |
44 | imports: [ | 49 | imports: [ |
45 | BrowserModule, | 50 | BrowserModule, |
diff --git a/client/src/app/login/login.component.html b/client/src/app/login/login.component.html index 4efe3fb22..683355960 100644 --- a/client/src/app/login/login.component.html +++ b/client/src/app/login/login.component.html | |||
@@ -23,10 +23,11 @@ | |||
23 | or create an account on another instance | 23 | or create an account on another instance |
24 | </a> | 24 | </a> |
25 | 25 | ||
26 | <my-help | 26 | <my-help *ngIf="signupAllowed === false"> |
27 | *ngIf="signupAllowed === false" helpType="custom" i18n-customHtml | 27 | <ng-template ptTemplate="customHtml"> |
28 | customHtml="User registration is not allowed on this instance, but you can register on many others!" | 28 | <ng-container i18n>User registration is not allowed on this instance, but you can register on many others!</ng-container> |
29 | ></my-help> | 29 | </ng-template> |
30 | </my-help> | ||
30 | </div> | 31 | </div> |
31 | 32 | ||
32 | <div *ngIf="formErrors.username" class="form-error"> | 33 | <div *ngIf="formErrors.username" class="form-error"> |
diff --git a/client/src/app/modal/instance-config-warning-modal.component.html b/client/src/app/modal/instance-config-warning-modal.component.html new file mode 100644 index 000000000..64f14e69b --- /dev/null +++ b/client/src/app/modal/instance-config-warning-modal.component.html | |||
@@ -0,0 +1,45 @@ | |||
1 | <ng-template #modal let-hide="close"> | ||
2 | <div class="modal-header"> | ||
3 | <h4 i18n class="modal-title">Configuration warning!</h4> | ||
4 | <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon> | ||
5 | </div> | ||
6 | |||
7 | <div class="modal-body"> | ||
8 | |||
9 | <p i18n>Hello dear administrator. You enabled user registration on your instance but you did not configure the following fields:</p> | ||
10 | |||
11 | <ul> | ||
12 | <li i18n *ngIf="about.instance.name.toLowerCase() === 'peertube'">Instance name</li> | ||
13 | <li i18n *ngIf="isDefaultShortDescription(about.instance.shortDescription)">Instance short description</li> | ||
14 | |||
15 | <li i18n *ngIf="!about.instance.administrator">Who you are</li> | ||
16 | <li i18n *ngIf="!about.instance.maintenanceLifetime">How long you plan to maintain your instance</li> | ||
17 | <li i18n *ngIf="!about.instance.businessModel">How you plan to pay your instance</li> | ||
18 | |||
19 | <li i18n *ngIf="!about.instance.moderationInformation">How you will moderate your instance</li> | ||
20 | <li i18n *ngIf="!about.instance.terms">Instance terms</li> | ||
21 | </ul> | ||
22 | |||
23 | <p> | ||
24 | Please consider to configure these fields to help people to choose <strong>the appropriate instance</strong>. | ||
25 | Without them, your instance may not be referenced on <a target="_blank" rel="noopener noreferrer" href="https://joinpeertube.org">JoinPeerTube website</a>. | ||
26 | </p> | ||
27 | |||
28 | <div class="configure-instance"> | ||
29 | <a i18n href="/admin/config/edit-custom" target="_blank" rel="noopener noreferrer">Configure these fields</a> | ||
30 | </div> | ||
31 | |||
32 | </div> | ||
33 | |||
34 | <div class="modal-footer inputs"> | ||
35 | <my-peertube-checkbox | ||
36 | inputName="stopDisplayModal" [(ngModel)]="stopDisplayModal" | ||
37 | i18n-labelText labelText="Don't show me this warning anymore" | ||
38 | > | ||
39 | |||
40 | </my-peertube-checkbox> | ||
41 | |||
42 | <span i18n class="action-button action-button-cancel" (click)="hide()">Close</span> | ||
43 | </div> | ||
44 | |||
45 | </ng-template> | ||
diff --git a/client/src/app/modal/instance-config-warning-modal.component.scss b/client/src/app/modal/instance-config-warning-modal.component.scss new file mode 100644 index 000000000..ff62a1b9c --- /dev/null +++ b/client/src/app/modal/instance-config-warning-modal.component.scss | |||
@@ -0,0 +1,22 @@ | |||
1 | @import '_mixins'; | ||
2 | @import '_variables'; | ||
3 | |||
4 | .action-button-cancel { | ||
5 | margin-right: 0 !important; | ||
6 | } | ||
7 | |||
8 | .modal-body { | ||
9 | font-size: 15px; | ||
10 | } | ||
11 | |||
12 | li { | ||
13 | margin-bottom: 10px; | ||
14 | } | ||
15 | |||
16 | .configure-instance { | ||
17 | text-align: center; | ||
18 | font-weight: 600; | ||
19 | font-size: 18px; | ||
20 | margin-top: 40px; | ||
21 | margin-bottom: 10px; | ||
22 | } | ||
diff --git a/client/src/app/modal/instance-config-warning-modal.component.ts b/client/src/app/modal/instance-config-warning-modal.component.ts new file mode 100644 index 000000000..742a7dd41 --- /dev/null +++ b/client/src/app/modal/instance-config-warning-modal.component.ts | |||
@@ -0,0 +1,47 @@ | |||
1 | import { Component, ElementRef, ViewChild } from '@angular/core' | ||
2 | import { Notifier } from '@app/core' | ||
3 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | ||
4 | import { About } from '@shared/models/server' | ||
5 | import { UserService } from '@app/shared' | ||
6 | |||
7 | @Component({ | ||
8 | selector: 'my-instance-config-warning-modal', | ||
9 | templateUrl: './instance-config-warning-modal.component.html', | ||
10 | styleUrls: [ './instance-config-warning-modal.component.scss' ] | ||
11 | }) | ||
12 | export class InstanceConfigWarningModalComponent { | ||
13 | @ViewChild('modal', { static: true }) modal: ElementRef | ||
14 | |||
15 | stopDisplayModal = false | ||
16 | about: About | ||
17 | |||
18 | constructor ( | ||
19 | private userService: UserService, | ||
20 | private modalService: NgbModal, | ||
21 | private notifier: Notifier | ||
22 | ) { } | ||
23 | |||
24 | show (about: About) { | ||
25 | this.about = about | ||
26 | |||
27 | const ref = this.modalService.open(this.modal) | ||
28 | |||
29 | ref.result.finally(() => { | ||
30 | if (this.stopDisplayModal === true) this.doNotOpenAgain() | ||
31 | }) | ||
32 | } | ||
33 | |||
34 | isDefaultShortDescription (description: string) { | ||
35 | return description === 'PeerTube, a federated (ActivityPub) video streaming platform using P2P (BitTorrent) directly ' + | ||
36 | 'in the web browser with WebTorrent and Angular.' | ||
37 | } | ||
38 | |||
39 | private doNotOpenAgain () { | ||
40 | this.userService.updateMyProfile({ noInstanceConfigWarningModal: true }) | ||
41 | .subscribe( | ||
42 | () => console.log('We will not open the instance config warning modal again.'), | ||
43 | |||
44 | err => this.notifier.error(err.message) | ||
45 | ) | ||
46 | } | ||
47 | } | ||
diff --git a/client/src/app/modal/welcome-modal.component.html b/client/src/app/modal/welcome-modal.component.html new file mode 100644 index 000000000..09ff2163b --- /dev/null +++ b/client/src/app/modal/welcome-modal.component.html | |||
@@ -0,0 +1,67 @@ | |||
1 | <ng-template #modal let-hide="close"> | ||
2 | <div class="modal-header"> | ||
3 | <h4 i18n class="modal-title">Welcome on PeerTube dear administrator!</h4> | ||
4 | <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon> | ||
5 | </div> | ||
6 | |||
7 | <div class="modal-body"> | ||
8 | |||
9 | <div class="block-documentation"> | ||
10 | <div i18n class="subtitle">Documentation</div> | ||
11 | |||
12 | <div class="columns"> | ||
13 | <a class="link-block" href="https://docs.joinpeertube.org/#/maintain-tools" target="_blank" rel="noopener noreferrer"> | ||
14 | <a class="link-title" href="https://docs.joinpeertube.org/#/maintain-tools" target="_blank" rel="noopener noreferrer">CLI</a> | ||
15 | |||
16 | <div>Upload or import videos, parse logs, prune storage directories, reset user password...</div> | ||
17 | </a> | ||
18 | |||
19 | <a class="link-block" href="https://docs.joinpeertube.org/#/admin-following-instances" target="_blank" rel="noopener noreferrer"> | ||
20 | <a class="link-title" href="https://docs.joinpeertube.org/#/admin-following-instances" target="_blank" rel="noopener noreferrer">Administer</a> | ||
21 | |||
22 | <div>Managing users, following other instances, dealing with spammers...</div> | ||
23 | </a> | ||
24 | |||
25 | <a class="link-block" href="https://docs.joinpeertube.org/#/use-setup-account" target="_blank" rel="noopener noreferrer"> | ||
26 | <a class="link-title" href="https://docs.joinpeertube.org/#/use-setup-account" target="_blank" rel="noopener noreferrer">Use</a> | ||
27 | |||
28 | <div>Setup your account, managing video playlists, discover third-party applications...</div> | ||
29 | </a> | ||
30 | </div> | ||
31 | </div> | ||
32 | |||
33 | <div class="block-configuration"> | ||
34 | <div i18n class="subtitle">It's time to configure your instance!</div> | ||
35 | |||
36 | <p i18n> | ||
37 | Choosing your <strong>instance name</strong>, <strong>setting up a description</strong>, specifying <strong>who you are</strong>, | ||
38 | why <strong>you created your instance</strong> and <strong>how long</strong> you plan to <strong>maintain your it</strong> | ||
39 | is very important for visitors to understand on what type of instance they are. | ||
40 | </p> | ||
41 | |||
42 | <p i18n> | ||
43 | If you want to open registrations, please decide what are <strong>your moderation rules</strong>, fill your <strong>instance terms</strong> | ||
44 | and specify the categories and languages you speak. This way, you will help users to register on <strong>the appropriate</strong> PeerTube instance. | ||
45 | </p> | ||
46 | |||
47 | <div class="configure-instance"> | ||
48 | <a i18n href="/admin/config/edit-custom" target="_blank" rel="noopener noreferrer">Configure your instance</a> | ||
49 | </div> | ||
50 | </div> | ||
51 | |||
52 | <div class="block-links"> | ||
53 | <div i18n class="subtitle">Useful links</div> | ||
54 | |||
55 | <ul> | ||
56 | <li>Official PeerTube website (news, support, contribute...): <a href="https://joinpeertube.org" target="_blank" rel="noopener noreferrer">https://joinpeertube.org</a></li> | ||
57 | |||
58 | <li>Put your instance on the public PeerTube index: <a href="https://instances.joinpeertube.org/instances">https://instances.joinpeertube.org/instances</a></li> | ||
59 | </ul> | ||
60 | </div> | ||
61 | </div> | ||
62 | |||
63 | <div class="modal-footer inputs"> | ||
64 | <span i18n class="action-button action-button-submit" (click)="hide()">Understood!</span> | ||
65 | </div> | ||
66 | |||
67 | </ng-template> | ||
diff --git a/client/src/app/modal/welcome-modal.component.scss b/client/src/app/modal/welcome-modal.component.scss new file mode 100644 index 000000000..8bb6973f4 --- /dev/null +++ b/client/src/app/modal/welcome-modal.component.scss | |||
@@ -0,0 +1,56 @@ | |||
1 | @import '_mixins'; | ||
2 | @import '_variables'; | ||
3 | |||
4 | .modal-body { | ||
5 | font-size: 15px; | ||
6 | } | ||
7 | |||
8 | .subtitle { | ||
9 | font-weight: $font-semibold; | ||
10 | margin-bottom: 10px; | ||
11 | font-size: 16px; | ||
12 | } | ||
13 | |||
14 | .block-documentation .subtitle { | ||
15 | margin-bottom: 20px; | ||
16 | } | ||
17 | |||
18 | .block-configuration, | ||
19 | .block-instance { | ||
20 | margin-top: 30px; | ||
21 | } | ||
22 | |||
23 | li { | ||
24 | margin-bottom: 10px; | ||
25 | } | ||
26 | |||
27 | .configure-instance { | ||
28 | text-align: center; | ||
29 | font-weight: 600; | ||
30 | font-size: 18px; | ||
31 | margin: 20px 0 40px 0; | ||
32 | } | ||
33 | |||
34 | .columns { | ||
35 | display: flex; | ||
36 | |||
37 | .link-block { | ||
38 | @include disable-default-a-behaviour; | ||
39 | |||
40 | color: var(--mainForegroundColor); | ||
41 | padding: 10px; | ||
42 | transition: background-color 0.2s ease-in; | ||
43 | |||
44 | &:hover { | ||
45 | background-color: rgba(0, 0, 0, 0.05); | ||
46 | } | ||
47 | |||
48 | .link-title { | ||
49 | font-size: 16px; | ||
50 | font-weight: $font-semibold; | ||
51 | display: flex; | ||
52 | justify-content: center; | ||
53 | margin-bottom: 5px; | ||
54 | } | ||
55 | } | ||
56 | } | ||
diff --git a/client/src/app/modal/welcome-modal.component.ts b/client/src/app/modal/welcome-modal.component.ts new file mode 100644 index 000000000..05412a4cd --- /dev/null +++ b/client/src/app/modal/welcome-modal.component.ts | |||
@@ -0,0 +1,38 @@ | |||
1 | import { Component, ElementRef, ViewChild } from '@angular/core' | ||
2 | import { Notifier } from '@app/core' | ||
3 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | ||
4 | import { UserService } from '@app/shared' | ||
5 | |||
6 | @Component({ | ||
7 | selector: 'my-welcome-modal', | ||
8 | templateUrl: './welcome-modal.component.html', | ||
9 | styleUrls: [ './welcome-modal.component.scss' ] | ||
10 | }) | ||
11 | export class WelcomeModalComponent { | ||
12 | @ViewChild('modal', { static: true }) modal: ElementRef | ||
13 | |||
14 | constructor ( | ||
15 | private userService: UserService, | ||
16 | private modalService: NgbModal, | ||
17 | private notifier: Notifier | ||
18 | ) { } | ||
19 | |||
20 | show () { | ||
21 | const ref = this.modalService.open(this.modal,{ | ||
22 | backdrop: 'static', | ||
23 | keyboard: false, | ||
24 | size: 'lg' | ||
25 | }) | ||
26 | |||
27 | ref.result.finally(() => this.doNotOpenAgain()) | ||
28 | } | ||
29 | |||
30 | private doNotOpenAgain () { | ||
31 | this.userService.updateMyProfile({ noWelcomeModal: true }) | ||
32 | .subscribe( | ||
33 | () => console.log('We will not open the welcome modal again.'), | ||
34 | |||
35 | err => this.notifier.error(err.message) | ||
36 | ) | ||
37 | } | ||
38 | } | ||
diff --git a/client/src/app/shared/angular/peertube-template.directive.ts b/client/src/app/shared/angular/peertube-template.directive.ts index a514b6057..e04c25d9a 100644 --- a/client/src/app/shared/angular/peertube-template.directive.ts +++ b/client/src/app/shared/angular/peertube-template.directive.ts | |||
@@ -3,8 +3,8 @@ import { Directive, Input, TemplateRef } from '@angular/core' | |||
3 | @Directive({ | 3 | @Directive({ |
4 | selector: '[ptTemplate]' | 4 | selector: '[ptTemplate]' |
5 | }) | 5 | }) |
6 | export class PeerTubeTemplateDirective { | 6 | export class PeerTubeTemplateDirective <T extends string> { |
7 | @Input('ptTemplate') name: string | 7 | @Input('ptTemplate') name: T |
8 | 8 | ||
9 | constructor (public template: TemplateRef<any>) { | 9 | constructor (public template: TemplateRef<any>) { |
10 | // empty | 10 | // empty |
diff --git a/client/src/app/shared/forms/form-validators/custom-config-validators.service.ts b/client/src/app/shared/forms/form-validators/custom-config-validators.service.ts index 882e39453..767e3f026 100644 --- a/client/src/app/shared/forms/form-validators/custom-config-validators.service.ts +++ b/client/src/app/shared/forms/form-validators/custom-config-validators.service.ts | |||
@@ -13,6 +13,7 @@ export class CustomConfigValidatorsService { | |||
13 | readonly SIGNUP_LIMIT: BuildFormValidator | 13 | readonly SIGNUP_LIMIT: BuildFormValidator |
14 | readonly ADMIN_EMAIL: BuildFormValidator | 14 | readonly ADMIN_EMAIL: BuildFormValidator |
15 | readonly TRANSCODING_THREADS: BuildFormValidator | 15 | readonly TRANSCODING_THREADS: BuildFormValidator |
16 | readonly INDEX_URL: BuildFormValidator | ||
16 | 17 | ||
17 | constructor (private i18n: I18n) { | 18 | constructor (private i18n: I18n) { |
18 | this.INSTANCE_NAME = { | 19 | this.INSTANCE_NAME = { |
@@ -78,5 +79,13 @@ export class CustomConfigValidatorsService { | |||
78 | 'min': this.i18n('Transcoding threads must be greater or equal to 0.') | 79 | 'min': this.i18n('Transcoding threads must be greater or equal to 0.') |
79 | } | 80 | } |
80 | } | 81 | } |
82 | |||
83 | this.INDEX_URL = { | ||
84 | VALIDATORS: [ Validators.required, Validators.pattern(/^https:\/\//) ], | ||
85 | MESSAGES: { | ||
86 | 'required': this.i18n('Index URL is required.'), | ||
87 | 'pattern': this.i18n('Index URL should be a URL') | ||
88 | } | ||
89 | } | ||
81 | } | 90 | } |
82 | } | 91 | } |
diff --git a/client/src/app/shared/forms/peertube-checkbox.component.html b/client/src/app/shared/forms/peertube-checkbox.component.html index 571a1a673..f1e3bf0bf 100644 --- a/client/src/app/shared/forms/peertube-checkbox.component.html +++ b/client/src/app/shared/forms/peertube-checkbox.component.html | |||
@@ -3,8 +3,15 @@ | |||
3 | <input type="checkbox" [(ngModel)]="checked" (ngModelChange)="onModelChange()" [id]="inputName" [disabled]="disabled" /> | 3 | <input type="checkbox" [(ngModel)]="checked" (ngModelChange)="onModelChange()" [id]="inputName" [disabled]="disabled" /> |
4 | <span role="checkbox" [attr.aria-checked]="checked"></span> | 4 | <span role="checkbox" [attr.aria-checked]="checked"></span> |
5 | <span *ngIf="labelText">{{ labelText }}</span> | 5 | <span *ngIf="labelText">{{ labelText }}</span> |
6 | <span *ngIf="labelHtml" [innerHTML]="labelHtml"></span> | 6 | |
7 | <span *ngIf="labelTemplate"> | ||
8 | <ng-container *ngTemplateOutlet="labelTemplate"></ng-container> | ||
9 | </span> | ||
7 | </label> | 10 | </label> |
8 | 11 | ||
9 | <my-help *ngIf="helpHtml" [tooltipPlacement]="helpPlacement" helpType="custom" i18n-customHtml [customHtml]="helpHtml"></my-help> | 12 | <my-help *ngIf="helpTemplate" [tooltipPlacement]="helpPlacement" helpType="custom"> |
13 | <ng-template ptTemplate="customHtml"> | ||
14 | <ng-template *ngTemplateOutlet="helpTemplate"></ng-template> | ||
15 | </ng-template> | ||
16 | </my-help> | ||
10 | </div> | 17 | </div> |
diff --git a/client/src/app/shared/forms/peertube-checkbox.component.scss b/client/src/app/shared/forms/peertube-checkbox.component.scss index 84ea788af..51f98b0bc 100644 --- a/client/src/app/shared/forms/peertube-checkbox.component.scss +++ b/client/src/app/shared/forms/peertube-checkbox.component.scss | |||
@@ -7,7 +7,7 @@ | |||
7 | .form-group-checkbox { | 7 | .form-group-checkbox { |
8 | display: flex; | 8 | display: flex; |
9 | 9 | ||
10 | span { | 10 | .label-text { |
11 | font-weight: $font-regular; | 11 | font-weight: $font-regular; |
12 | margin: 0; | 12 | margin: 0; |
13 | } | 13 | } |
diff --git a/client/src/app/shared/forms/peertube-checkbox.component.ts b/client/src/app/shared/forms/peertube-checkbox.component.ts index a4b72aa37..3b8f39ed0 100644 --- a/client/src/app/shared/forms/peertube-checkbox.component.ts +++ b/client/src/app/shared/forms/peertube-checkbox.component.ts | |||
@@ -1,5 +1,6 @@ | |||
1 | import { ChangeDetectorRef, Component, forwardRef, Input, OnChanges, SimpleChanges } from '@angular/core' | 1 | import { AfterContentInit, ChangeDetectorRef, Component, ContentChildren, forwardRef, Input, QueryList, TemplateRef } from '@angular/core' |
2 | import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms' | 2 | import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms' |
3 | import { PeerTubeTemplateDirective } from '@app/shared/angular/peertube-template.directive' | ||
3 | 4 | ||
4 | @Component({ | 5 | @Component({ |
5 | selector: 'my-peertube-checkbox', | 6 | selector: 'my-peertube-checkbox', |
@@ -13,20 +14,35 @@ import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms' | |||
13 | } | 14 | } |
14 | ] | 15 | ] |
15 | }) | 16 | }) |
16 | export class PeertubeCheckboxComponent implements ControlValueAccessor { | 17 | export class PeertubeCheckboxComponent implements ControlValueAccessor, AfterContentInit { |
17 | @Input() checked = false | 18 | @Input() checked = false |
18 | @Input() inputName: string | 19 | @Input() inputName: string |
19 | @Input() labelText: string | 20 | @Input() labelText: string |
20 | @Input() labelHtml: string | ||
21 | @Input() helpHtml: string | ||
22 | @Input() helpPlacement = 'top' | 21 | @Input() helpPlacement = 'top' |
23 | @Input() disabled = false | 22 | @Input() disabled = false |
24 | 23 | ||
24 | @ContentChildren(PeerTubeTemplateDirective) templates: QueryList<PeerTubeTemplateDirective<'label' | 'help'>> | ||
25 | |||
25 | // FIXME: https://github.com/angular/angular/issues/10816#issuecomment-307567836 | 26 | // FIXME: https://github.com/angular/angular/issues/10816#issuecomment-307567836 |
26 | @Input() onPushWorkaround = false | 27 | @Input() onPushWorkaround = false |
27 | 28 | ||
29 | labelTemplate: TemplateRef<any> | ||
30 | helpTemplate: TemplateRef<any> | ||
31 | |||
28 | constructor (private cdr: ChangeDetectorRef) { } | 32 | constructor (private cdr: ChangeDetectorRef) { } |
29 | 33 | ||
34 | ngAfterContentInit () { | ||
35 | { | ||
36 | const t = this.templates.find(t => t.name === 'label') | ||
37 | if (t) this.labelTemplate = t.template | ||
38 | } | ||
39 | |||
40 | { | ||
41 | const t = this.templates.find(t => t.name === 'help') | ||
42 | if (t) this.helpTemplate = t.template | ||
43 | } | ||
44 | } | ||
45 | |||
30 | propagateChange = (_: any) => { /* empty */ } | 46 | propagateChange = (_: any) => { /* empty */ } |
31 | 47 | ||
32 | writeValue (checked: boolean) { | 48 | writeValue (checked: boolean) { |
diff --git a/client/src/app/shared/instance/feature-boolean.component.html b/client/src/app/shared/instance/feature-boolean.component.html new file mode 100644 index 000000000..ac208fc13 --- /dev/null +++ b/client/src/app/shared/instance/feature-boolean.component.html | |||
@@ -0,0 +1,3 @@ | |||
1 | <span *ngIf="value === true" class="glyphicon glyphicon-ok"></span> | ||
2 | <span *ngIf="value === false" class="glyphicon glyphicon-remove"></span> | ||
3 | |||
diff --git a/client/src/app/shared/instance/feature-boolean.component.scss b/client/src/app/shared/instance/feature-boolean.component.scss new file mode 100644 index 000000000..56d08af06 --- /dev/null +++ b/client/src/app/shared/instance/feature-boolean.component.scss | |||
@@ -0,0 +1,10 @@ | |||
1 | @import '_variables'; | ||
2 | @import '_mixins'; | ||
3 | |||
4 | .glyphicon-ok { | ||
5 | color: $green; | ||
6 | } | ||
7 | |||
8 | .glyphicon-remove { | ||
9 | color: $red; | ||
10 | } | ||
diff --git a/client/src/app/shared/instance/feature-boolean.component.ts b/client/src/app/shared/instance/feature-boolean.component.ts new file mode 100644 index 000000000..d02d513d6 --- /dev/null +++ b/client/src/app/shared/instance/feature-boolean.component.ts | |||
@@ -0,0 +1,10 @@ | |||
1 | import { Component, Input } from '@angular/core' | ||
2 | |||
3 | @Component({ | ||
4 | selector: 'my-feature-boolean', | ||
5 | templateUrl: './feature-boolean.component.html', | ||
6 | styleUrls: [ './feature-boolean.component.scss' ] | ||
7 | }) | ||
8 | export class FeatureBooleanComponent { | ||
9 | @Input() value: boolean | ||
10 | } | ||
diff --git a/client/src/app/shared/instance/instance-features-table.component.html b/client/src/app/shared/instance/instance-features-table.component.html index 2987bd00e..d1cb8fcbe 100644 --- a/client/src/app/shared/instance/instance-features-table.component.html +++ b/client/src/app/shared/instance/instance-features-table.component.html | |||
@@ -1,28 +1,53 @@ | |||
1 | <div class="feature-table"> | 1 | <div class="feature-table"> |
2 | 2 | ||
3 | <table class="table"> | 3 | <table class="table" *ngIf="config"> |
4 | <tr> | 4 | <tr> |
5 | <td i18n class="label">Default NSFW/sensitive videos policy (can be redefined by the users)</td> | 5 | <td i18n class="label"> |
6 | <div>Default NSFW/sensitive videos policy</div> | ||
7 | <div class="more-info">can be redefined by the users</div> | ||
8 | </td> | ||
6 | 9 | ||
7 | <td class="value">{{ buildNSFWLabel() }}</td> | 10 | <td class="value">{{ buildNSFWLabel() }}</td> |
8 | </tr> | 11 | </tr> |
9 | 12 | ||
10 | <tr *ngFor="let feature of features"> | 13 | <tr> |
11 | <td class="label">{{ feature.label }}</td> | 14 | <td i18n class="label">User registration allowed</td> |
12 | <td> | 15 | <td> |
13 | <span *ngIf="feature.value === true" class="glyphicon glyphicon-ok"></span> | 16 | <my-feature-boolean [value]="config.signup.allowed"></my-feature-boolean> |
14 | <span *ngIf="feature.value === false" class="glyphicon glyphicon-remove"></span> | ||
15 | </td> | 17 | </td> |
16 | </tr> | 18 | </tr> |
17 | 19 | ||
18 | <tr> | 20 | <tr> |
19 | <td i18n class="label">Video quota</td> | 21 | <td i18n class="label" colspan="2">Video uploads</td> |
22 | </tr> | ||
23 | |||
24 | <tr> | ||
25 | <td i18n class="sub-label">Transcoding in multiple resolutions</td> | ||
26 | <td> | ||
27 | <my-feature-boolean [value]="config.transcoding.enabledResolutions.length !== 0"></my-feature-boolean> | ||
28 | </td> | ||
29 | </tr> | ||
30 | |||
31 | <tr> | ||
32 | <td i18n class="sub-label">Video uploads</td> | ||
33 | <td> | ||
34 | <span *ngIf="config.autoBlacklist.videos.ofUsers.enabled">Requires manual validation by moderators</span> | ||
35 | <span *ngIf="!config.autoBlacklist.videos.ofUsers.enabled">Automatically published</span> | ||
36 | </td> | ||
37 | </tr> | ||
38 | |||
39 | <tr> | ||
40 | <td i18n class="sub-label">Video quota</td> | ||
20 | 41 | ||
21 | <td class="value"> | 42 | <td class="value"> |
22 | <ng-container *ngIf="initialUserVideoQuota !== -1"> | 43 | <ng-container *ngIf="initialUserVideoQuota !== -1"> |
23 | {{ initialUserVideoQuota | bytes: 0 }} <ng-container *ngIf="dailyUserVideoQuota !== -1">({{ dailyUserVideoQuota | bytes: 0 }} per day)</ng-container> | 44 | {{ initialUserVideoQuota | bytes: 0 }} <ng-container *ngIf="dailyUserVideoQuota !== -1">({{ dailyUserVideoQuota | bytes: 0 }} per day)</ng-container> |
24 | 45 | ||
25 | <my-help tooltipPlacement="auto" helpType="custom" [customHtml]="quotaHelpIndication"></my-help> | 46 | <my-help tooltipPlacement="auto" helpType="custom"> |
47 | <ng-template ptTemplate="customHtml"> | ||
48 | <div [innerHTML]="quotaHelpIndication"></div> | ||
49 | </ng-template> | ||
50 | </my-help> | ||
26 | </ng-container> | 51 | </ng-container> |
27 | 52 | ||
28 | <ng-container i18n *ngIf="initialUserVideoQuota === -1"> | 53 | <ng-container i18n *ngIf="initialUserVideoQuota === -1"> |
@@ -30,5 +55,35 @@ | |||
30 | </ng-container> | 55 | </ng-container> |
31 | </td> | 56 | </td> |
32 | </tr> | 57 | </tr> |
58 | |||
59 | <tr> | ||
60 | <td i18n class="label" colspan="2">Import</td> | ||
61 | </tr> | ||
62 | |||
63 | <tr> | ||
64 | <td i18n class="sub-label">HTTP import (YouTube, Vimeo, direct URL...)</td> | ||
65 | <td> | ||
66 | <my-feature-boolean [value]="config.import.videos.http.enabled"></my-feature-boolean> | ||
67 | </td> | ||
68 | </tr> | ||
69 | |||
70 | <tr> | ||
71 | <td i18n class="sub-label">Torrent import</td> | ||
72 | <td> | ||
73 | <my-feature-boolean [value]="config.import.videos.torrent.enabled"></my-feature-boolean> | ||
74 | </td> | ||
75 | </tr> | ||
76 | |||
77 | |||
78 | <tr> | ||
79 | <td i18n class="label" colspan="2">Player</td> | ||
80 | </tr> | ||
81 | |||
82 | <tr> | ||
83 | <td i18n class="sub-label">P2P enabled</td> | ||
84 | <td> | ||
85 | <my-feature-boolean [value]="config.tracker.enabled"></my-feature-boolean> | ||
86 | </td> | ||
87 | </tr> | ||
33 | </table> | 88 | </table> |
34 | </div> | 89 | </div> |
diff --git a/client/src/app/shared/instance/instance-features-table.component.scss b/client/src/app/shared/instance/instance-features-table.component.scss index f9bec038d..67f2b6c84 100644 --- a/client/src/app/shared/instance/instance-features-table.component.scss +++ b/client/src/app/shared/instance/instance-features-table.component.scss | |||
@@ -5,16 +5,28 @@ table { | |||
5 | font-size: 14px; | 5 | font-size: 14px; |
6 | color: var(--mainForegroundColor); | 6 | color: var(--mainForegroundColor); |
7 | 7 | ||
8 | .label { | 8 | .label, |
9 | font-weight: $font-semibold; | 9 | .sub-label { |
10 | min-width: 330px; | 10 | min-width: 330px; |
11 | } | ||
12 | 11 | ||
13 | .glyphicon-ok { | 12 | &.label { |
14 | color: $green; | 13 | font-weight: $font-semibold; |
14 | } | ||
15 | |||
16 | &.sub-label { | ||
17 | padding-left: 30px; | ||
18 | } | ||
19 | |||
20 | .more-info { | ||
21 | font-style: italic; | ||
22 | font-weight: initial; | ||
23 | font-size: 14px | ||
24 | } | ||
15 | } | 25 | } |
16 | 26 | ||
17 | .glyphicon-remove { | 27 | td { |
18 | color: $red; | 28 | vertical-align: middle; |
19 | } | 29 | } |
20 | } | 30 | } |
31 | |||
32 | |||
diff --git a/client/src/app/shared/instance/instance-features-table.component.ts b/client/src/app/shared/instance/instance-features-table.component.ts index a53082a93..46df4d0b2 100644 --- a/client/src/app/shared/instance/instance-features-table.component.ts +++ b/client/src/app/shared/instance/instance-features-table.component.ts | |||
@@ -1,6 +1,7 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | 1 | import { Component, OnInit } from '@angular/core' |
2 | import { ServerService } from '@app/core' | 2 | import { ServerService } from '@app/core' |
3 | import { I18n } from '@ngx-translate/i18n-polyfill' | 3 | import { I18n } from '@ngx-translate/i18n-polyfill' |
4 | import { ServerConfig } from '@shared/models' | ||
4 | 5 | ||
5 | @Component({ | 6 | @Component({ |
6 | selector: 'my-instance-features-table', | 7 | selector: 'my-instance-features-table', |
@@ -8,8 +9,8 @@ import { I18n } from '@ngx-translate/i18n-polyfill' | |||
8 | styleUrls: [ './instance-features-table.component.scss' ] | 9 | styleUrls: [ './instance-features-table.component.scss' ] |
9 | }) | 10 | }) |
10 | export class InstanceFeaturesTableComponent implements OnInit { | 11 | export class InstanceFeaturesTableComponent implements OnInit { |
11 | features: { label: string, value?: boolean }[] = [] | ||
12 | quotaHelpIndication = '' | 12 | quotaHelpIndication = '' |
13 | config: ServerConfig | ||
13 | 14 | ||
14 | constructor ( | 15 | constructor ( |
15 | private i18n: I18n, | 16 | private i18n: I18n, |
@@ -28,7 +29,7 @@ export class InstanceFeaturesTableComponent implements OnInit { | |||
28 | ngOnInit () { | 29 | ngOnInit () { |
29 | this.serverService.configLoaded | 30 | this.serverService.configLoaded |
30 | .subscribe(() => { | 31 | .subscribe(() => { |
31 | this.buildFeatures() | 32 | this.config = this.serverService.getConfig() |
32 | this.buildQuotaHelpIndication() | 33 | this.buildQuotaHelpIndication() |
33 | }) | 34 | }) |
34 | } | 35 | } |
@@ -41,37 +42,6 @@ export class InstanceFeaturesTableComponent implements OnInit { | |||
41 | if (policy === 'display') return this.i18n('Displayed') | 42 | if (policy === 'display') return this.i18n('Displayed') |
42 | } | 43 | } |
43 | 44 | ||
44 | private buildFeatures () { | ||
45 | const config = this.serverService.getConfig() | ||
46 | |||
47 | this.features = [ | ||
48 | { | ||
49 | label: this.i18n('User registration allowed'), | ||
50 | value: config.signup.allowed | ||
51 | }, | ||
52 | { | ||
53 | label: this.i18n('Video uploads require manual validation by moderators'), | ||
54 | value: config.autoBlacklist.videos.ofUsers.enabled | ||
55 | }, | ||
56 | { | ||
57 | label: this.i18n('Transcode your videos in multiple resolutions'), | ||
58 | value: config.transcoding.enabledResolutions.length !== 0 | ||
59 | }, | ||
60 | { | ||
61 | label: this.i18n('HTTP import (YouTube, Vimeo, direct URL...)'), | ||
62 | value: config.import.videos.http.enabled | ||
63 | }, | ||
64 | { | ||
65 | label: this.i18n('Torrent import'), | ||
66 | value: config.import.videos.torrent.enabled | ||
67 | }, | ||
68 | { | ||
69 | label: this.i18n('P2P enabled'), | ||
70 | value: config.tracker.enabled | ||
71 | } | ||
72 | ] | ||
73 | } | ||
74 | |||
75 | private getApproximateTime (seconds: number) { | 45 | private getApproximateTime (seconds: number) { |
76 | const hours = Math.floor(seconds / 3600) | 46 | const hours = Math.floor(seconds / 3600) |
77 | let pluralSuffix = '' | 47 | let pluralSuffix = '' |
diff --git a/client/src/app/shared/instance/instance.service.ts b/client/src/app/shared/instance/instance.service.ts index d0c96941d..44b413fa4 100644 --- a/client/src/app/shared/instance/instance.service.ts +++ b/client/src/app/shared/instance/instance.service.ts | |||
@@ -4,6 +4,9 @@ import { Injectable } from '@angular/core' | |||
4 | import { environment } from '../../../environments/environment' | 4 | import { environment } from '../../../environments/environment' |
5 | import { RestExtractor, RestService } from '../rest' | 5 | import { RestExtractor, RestService } from '../rest' |
6 | import { About } from '../../../../../shared/models/server' | 6 | import { About } from '../../../../../shared/models/server' |
7 | import { MarkdownService } from '@app/shared/renderer' | ||
8 | import { peertubeTranslate } from '@shared/models' | ||
9 | import { ServerService } from '@app/core' | ||
7 | 10 | ||
8 | @Injectable() | 11 | @Injectable() |
9 | export class InstanceService { | 12 | export class InstanceService { |
@@ -13,7 +16,9 @@ export class InstanceService { | |||
13 | constructor ( | 16 | constructor ( |
14 | private authHttp: HttpClient, | 17 | private authHttp: HttpClient, |
15 | private restService: RestService, | 18 | private restService: RestService, |
16 | private restExtractor: RestExtractor | 19 | private restExtractor: RestExtractor, |
20 | private markdownService: MarkdownService, | ||
21 | private serverService: ServerService | ||
17 | ) { | 22 | ) { |
18 | } | 23 | } |
19 | 24 | ||
@@ -34,4 +39,43 @@ export class InstanceService { | |||
34 | .pipe(catchError(res => this.restExtractor.handleError(res))) | 39 | .pipe(catchError(res => this.restExtractor.handleError(res))) |
35 | 40 | ||
36 | } | 41 | } |
42 | |||
43 | async buildHtml (about: About) { | ||
44 | const html = { | ||
45 | description: '', | ||
46 | terms: '', | ||
47 | codeOfConduct: '', | ||
48 | moderationInformation: '', | ||
49 | administrator: '', | ||
50 | hardwareInformation: '' | ||
51 | } | ||
52 | |||
53 | for (const key of Object.keys(html)) { | ||
54 | html[ key ] = await this.markdownService.textMarkdownToHTML(about.instance[ key ]) | ||
55 | } | ||
56 | |||
57 | return html | ||
58 | } | ||
59 | |||
60 | buildTranslatedLanguages (about: About, translations: any) { | ||
61 | const languagesArray = this.serverService.getVideoLanguages() | ||
62 | |||
63 | return about.instance.languages | ||
64 | .map(l => { | ||
65 | const languageObj = languagesArray.find(la => la.id === l) | ||
66 | |||
67 | return peertubeTranslate(languageObj.label, translations) | ||
68 | }) | ||
69 | } | ||
70 | |||
71 | buildTranslatedCategories (about: About, translations: any) { | ||
72 | const categoriesArray = this.serverService.getVideoCategories() | ||
73 | |||
74 | return about.instance.categories | ||
75 | .map(c => { | ||
76 | const categoryObj = categoriesArray.find(ca => ca.id === c) | ||
77 | |||
78 | return peertubeTranslate(categoryObj.label, translations) | ||
79 | }) | ||
80 | } | ||
37 | } | 81 | } |
diff --git a/client/src/app/shared/misc/help.component.html b/client/src/app/shared/misc/help.component.html index e31eef06a..9a6d3e48e 100644 --- a/client/src/app/shared/misc/help.component.html +++ b/client/src/app/shared/misc/help.component.html | |||
@@ -1,15 +1,25 @@ | |||
1 | <ng-template #tooltipTemplate> | 1 | <ng-template #tooltipTemplate> |
2 | <ng-template [ngIf]="preHtml"> | 2 | <p *ngIf="preHtmlTemplate"> |
3 | <p [innerHTML]="preHtml"></p> | 3 | <ng-template *ngTemplateOutlet="preHtmlTemplate"></ng-template> |
4 | <br /> | 4 | </p> |
5 | </ng-template> | ||
6 | 5 | ||
7 | <p [innerHTML]="mainHtml"></p> | 6 | <ng-container *ngIf="preHtmlTemplate && (customHtmlTemplate || mainHtml || postHtmlTemplate)"> |
7 | <br /><br /> | ||
8 | </ng-container> | ||
8 | 9 | ||
9 | <ng-template [ngIf]="postHtml"> | 10 | <p *ngIf="customHtmlTemplate"> |
10 | <br /> | 11 | <ng-template *ngTemplateOutlet="customHtmlTemplate"></ng-template> |
11 | <p [innerHTML]="postHtml"></p> | 12 | </p> |
12 | </ng-template> | 13 | |
14 | <p *ngIf="mainHtml" [innerHTML]="mainHtml"></p> | ||
15 | |||
16 | <ng-container *ngIf="(customHtmlTemplate || mainHtml) && postHtmlTemplate"> | ||
17 | <br /><br /> | ||
18 | </ng-container> | ||
19 | |||
20 | <p *ngIf="postHtmlTemplate"> | ||
21 | <ng-template *ngTemplateOutlet="postHtmlTemplate"></ng-template> | ||
22 | </p> | ||
13 | </ng-template> | 23 | </ng-template> |
14 | 24 | ||
15 | <span | 25 | <span |
diff --git a/client/src/app/shared/misc/help.component.ts b/client/src/app/shared/misc/help.component.ts index f3426f70f..18ba8ad5e 100644 --- a/client/src/app/shared/misc/help.component.ts +++ b/client/src/app/shared/misc/help.component.ts | |||
@@ -1,6 +1,7 @@ | |||
1 | import { Component, Input, OnChanges, OnInit } from '@angular/core' | 1 | import { AfterContentInit, Component, ContentChildren, Input, OnChanges, OnInit, QueryList, TemplateRef } from '@angular/core' |
2 | import { I18n } from '@ngx-translate/i18n-polyfill' | 2 | import { I18n } from '@ngx-translate/i18n-polyfill' |
3 | import { MarkdownService } from '@app/shared/renderer' | 3 | import { MarkdownService } from '@app/shared/renderer' |
4 | import { PeerTubeTemplateDirective } from '@app/shared/angular/peertube-template.directive' | ||
4 | 5 | ||
5 | @Component({ | 6 | @Component({ |
6 | selector: 'my-help', | 7 | selector: 'my-help', |
@@ -8,22 +9,42 @@ import { MarkdownService } from '@app/shared/renderer' | |||
8 | templateUrl: './help.component.html' | 9 | templateUrl: './help.component.html' |
9 | }) | 10 | }) |
10 | 11 | ||
11 | export class HelpComponent implements OnInit, OnChanges { | 12 | export class HelpComponent implements OnInit, OnChanges, AfterContentInit { |
12 | @Input() preHtml = '' | ||
13 | @Input() postHtml = '' | ||
14 | @Input() customHtml = '' | ||
15 | @Input() helpType: 'custom' | 'markdownText' | 'markdownEnhanced' = 'custom' | 13 | @Input() helpType: 'custom' | 'markdownText' | 'markdownEnhanced' = 'custom' |
16 | @Input() tooltipPlacement = 'right' | 14 | @Input() tooltipPlacement = 'right' |
17 | 15 | ||
16 | @ContentChildren(PeerTubeTemplateDirective) templates: QueryList<PeerTubeTemplateDirective<'preHtml' | 'customHtml' | 'postHtml'>> | ||
17 | |||
18 | isPopoverOpened = false | 18 | isPopoverOpened = false |
19 | mainHtml = '' | 19 | mainHtml = '' |
20 | 20 | ||
21 | preHtmlTemplate: TemplateRef<any> | ||
22 | customHtmlTemplate: TemplateRef<any> | ||
23 | postHtmlTemplate: TemplateRef<any> | ||
24 | |||
21 | constructor (private i18n: I18n) { } | 25 | constructor (private i18n: I18n) { } |
22 | 26 | ||
23 | ngOnInit () { | 27 | ngOnInit () { |
24 | this.init() | 28 | this.init() |
25 | } | 29 | } |
26 | 30 | ||
31 | ngAfterContentInit () { | ||
32 | { | ||
33 | const t = this.templates.find(t => t.name === 'preHtml') | ||
34 | if (t) this.preHtmlTemplate = t.template | ||
35 | } | ||
36 | |||
37 | { | ||
38 | const t = this.templates.find(t => t.name === 'customHtml') | ||
39 | if (t) this.customHtmlTemplate = t.template | ||
40 | } | ||
41 | |||
42 | { | ||
43 | const t = this.templates.find(t => t.name === 'postHtml') | ||
44 | if (t) this.postHtmlTemplate = t.template | ||
45 | } | ||
46 | } | ||
47 | |||
27 | ngOnChanges () { | 48 | ngOnChanges () { |
28 | this.init() | 49 | this.init() |
29 | } | 50 | } |
@@ -37,11 +58,6 @@ export class HelpComponent implements OnInit, OnChanges { | |||
37 | } | 58 | } |
38 | 59 | ||
39 | private init () { | 60 | private init () { |
40 | if (this.helpType === 'custom') { | ||
41 | this.mainHtml = this.customHtml | ||
42 | return | ||
43 | } | ||
44 | |||
45 | if (this.helpType === 'markdownText') { | 61 | if (this.helpType === 'markdownText') { |
46 | this.mainHtml = this.formatMarkdownSupport(MarkdownService.TEXT_RULES) | 62 | this.mainHtml = this.formatMarkdownSupport(MarkdownService.TEXT_RULES) |
47 | return | 63 | return |
diff --git a/client/src/app/shared/renderer/markdown.service.ts b/client/src/app/shared/renderer/markdown.service.ts index 9a9066351..0e24f3085 100644 --- a/client/src/app/shared/renderer/markdown.service.ts +++ b/client/src/app/shared/renderer/markdown.service.ts | |||
@@ -13,9 +13,11 @@ export class MarkdownService { | |||
13 | 'list' | 13 | 'list' |
14 | ] | 14 | ] |
15 | static ENHANCED_RULES = MarkdownService.TEXT_RULES.concat([ 'image' ]) | 15 | static ENHANCED_RULES = MarkdownService.TEXT_RULES.concat([ 'image' ]) |
16 | static COMPLETE_RULES = MarkdownService.ENHANCED_RULES.concat([ 'block', 'inline', 'heading', 'html_inline', 'html_block', 'paragraph' ]) | ||
16 | 17 | ||
17 | private textMarkdownIt: MarkdownIt | 18 | private textMarkdownIt: MarkdownIt |
18 | private enhancedMarkdownIt: MarkdownIt | 19 | private enhancedMarkdownIt: MarkdownIt |
20 | private completeMarkdownIt: MarkdownIt | ||
19 | 21 | ||
20 | async textMarkdownToHTML (markdown: string) { | 22 | async textMarkdownToHTML (markdown: string) { |
21 | if (!markdown) return '' | 23 | if (!markdown) return '' |
@@ -39,11 +41,22 @@ export class MarkdownService { | |||
39 | return this.avoidTruncatedTags(html) | 41 | return this.avoidTruncatedTags(html) |
40 | } | 42 | } |
41 | 43 | ||
42 | private async createMarkdownIt (rules: string[]) { | 44 | async completeMarkdownToHTML (markdown: string) { |
43 | // FIXME: import('..') returns a struct module, containing a "default" field corresponding to our sanitizeHtml function | 45 | if (!markdown) return '' |
46 | |||
47 | if (!this.completeMarkdownIt) { | ||
48 | this.completeMarkdownIt = await this.createMarkdownIt(MarkdownService.COMPLETE_RULES, true) | ||
49 | } | ||
50 | |||
51 | const html = this.completeMarkdownIt.render(markdown) | ||
52 | return this.avoidTruncatedTags(html) | ||
53 | } | ||
54 | |||
55 | private async createMarkdownIt (rules: string[], html = false) { | ||
56 | // FIXME: import('...') returns a struct module, containing a "default" field corresponding to our sanitizeHtml function | ||
44 | const MarkdownItClass: typeof import ('markdown-it') = (await import('markdown-it') as any).default | 57 | const MarkdownItClass: typeof import ('markdown-it') = (await import('markdown-it') as any).default |
45 | 58 | ||
46 | const markdownIt = new MarkdownItClass('zero', { linkify: true, breaks: true }) | 59 | const markdownIt = new MarkdownItClass('zero', { linkify: true, breaks: true, html }) |
47 | 60 | ||
48 | for (const rule of rules) { | 61 | for (const rule of rules) { |
49 | markdownIt.enable(rule) | 62 | markdownIt.enable(rule) |
diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts index eb57a2fff..65e0f21a4 100644 --- a/client/src/app/shared/shared.module.ts +++ b/client/src/app/shared/shared.module.ts | |||
@@ -6,10 +6,8 @@ import { RouterModule } from '@angular/router' | |||
6 | import { MarkdownTextareaComponent } from '@app/shared/forms/markdown-textarea.component' | 6 | import { MarkdownTextareaComponent } from '@app/shared/forms/markdown-textarea.component' |
7 | import { HelpComponent } from '@app/shared/misc/help.component' | 7 | import { HelpComponent } from '@app/shared/misc/help.component' |
8 | import { InfiniteScrollerDirective } from '@app/shared/video/infinite-scroller.directive' | 8 | import { InfiniteScrollerDirective } from '@app/shared/video/infinite-scroller.directive' |
9 | |||
10 | import { BytesPipe, KeysPipe, NgPipesModule } from 'ngx-pipes' | 9 | import { BytesPipe, KeysPipe, NgPipesModule } from 'ngx-pipes' |
11 | import { SharedModule as PrimeSharedModule } from 'primeng/components/common/shared' | 10 | import { SharedModule as PrimeSharedModule } from 'primeng/components/common/shared' |
12 | |||
13 | import { AUTH_INTERCEPTOR_PROVIDER } from './auth' | 11 | import { AUTH_INTERCEPTOR_PROVIDER } from './auth' |
14 | import { ButtonComponent } from './buttons/button.component' | 12 | import { ButtonComponent } from './buttons/button.component' |
15 | import { DeleteButtonComponent } from './buttons/delete-button.component' | 13 | import { DeleteButtonComponent } from './buttons/delete-button.component' |
@@ -93,6 +91,8 @@ import { VideoDownloadComponent } from '@app/shared/video/modals/video-download. | |||
93 | import { VideoReportComponent } from '@app/shared/video/modals/video-report.component' | 91 | import { VideoReportComponent } from '@app/shared/video/modals/video-report.component' |
94 | import { ClipboardModule } from 'ngx-clipboard' | 92 | import { ClipboardModule } from 'ngx-clipboard' |
95 | import { FollowService } from '@app/shared/instance/follow.service' | 93 | import { FollowService } from '@app/shared/instance/follow.service' |
94 | import { MultiSelectModule } from 'primeng/multiselect' | ||
95 | import { FeatureBooleanComponent } from '@app/shared/instance/feature-boolean.component' | ||
96 | 96 | ||
97 | @NgModule({ | 97 | @NgModule({ |
98 | imports: [ | 98 | imports: [ |
@@ -113,7 +113,8 @@ import { FollowService } from '@app/shared/instance/follow.service' | |||
113 | 113 | ||
114 | PrimeSharedModule, | 114 | PrimeSharedModule, |
115 | InputMaskModule, | 115 | InputMaskModule, |
116 | NgPipesModule | 116 | NgPipesModule, |
117 | MultiSelectModule | ||
117 | ], | 118 | ], |
118 | 119 | ||
119 | declarations: [ | 120 | declarations: [ |
@@ -156,6 +157,7 @@ import { FollowService } from '@app/shared/instance/follow.service' | |||
156 | SubscribeButtonComponent, | 157 | SubscribeButtonComponent, |
157 | RemoteSubscribeComponent, | 158 | RemoteSubscribeComponent, |
158 | InstanceFeaturesTableComponent, | 159 | InstanceFeaturesTableComponent, |
160 | FeatureBooleanComponent, | ||
159 | UserBanModalComponent, | 161 | UserBanModalComponent, |
160 | UserModerationDropdownComponent, | 162 | UserModerationDropdownComponent, |
161 | TopMenuDropdownComponent, | 163 | TopMenuDropdownComponent, |
@@ -186,6 +188,7 @@ import { FollowService } from '@app/shared/instance/follow.service' | |||
186 | InputMaskModule, | 188 | InputMaskModule, |
187 | BytesPipe, | 189 | BytesPipe, |
188 | KeysPipe, | 190 | KeysPipe, |
191 | MultiSelectModule, | ||
189 | 192 | ||
190 | LoaderComponent, | 193 | LoaderComponent, |
191 | SmallLoaderComponent, | 194 | SmallLoaderComponent, |
diff --git a/client/src/app/shared/user-subscription/remote-subscribe.component.html b/client/src/app/shared/user-subscription/remote-subscribe.component.html index ec3636b3e..59ee1cb04 100644 --- a/client/src/app/shared/user-subscription/remote-subscribe.component.html +++ b/client/src/app/shared/user-subscription/remote-subscribe.component.html | |||
@@ -12,13 +12,21 @@ | |||
12 | <span *ngIf="interact">Remote interact</span> | 12 | <span *ngIf="interact">Remote interact</span> |
13 | </button> | 13 | </button> |
14 | 14 | ||
15 | <my-help *ngIf="!interact && showHelp" | 15 | <my-help *ngIf="!interact && showHelp"> |
16 | helpType="custom" | 16 | <ng-template ptTemplate="customHtml"> |
17 | i18n-customHtml customHtml="You can subscribe to the channel via any ActivityPub-capable fediverse instance. For instance with Mastodon or Pleroma you can type the channel URL in the search box and subscribe there."> | 17 | <ng-container i18n> |
18 | You can subscribe to the channel via any ActivityPub-capable fediverse instance.<br /><br /> | ||
19 | For instance with Mastodon or Pleroma you can type the channel URL in the search box and subscribe there. | ||
20 | </ng-container> | ||
21 | </ng-template> | ||
18 | </my-help> | 22 | </my-help> |
19 | 23 | ||
20 | <my-help *ngIf="showHelp && interact" | 24 | <my-help *ngIf="showHelp && interact"> |
21 | helpType="custom" | 25 | <ng-template ptTemplate="customHtml"> |
22 | i18n-customHtml customHtml="You can interact with this via any ActivityPub-capable fediverse instance. For instance with Mastodon or Pleroma you can type the current URL in the search box and interact with it there."> | 26 | <ng-container i18n> |
27 | You can interact with this via any ActivityPub-capable fediverse instance.<br /><br /> | ||
28 | For instance with Mastodon or Pleroma you can type the current URL in the search box and interact with it there. | ||
29 | </ng-container> | ||
30 | </ng-template> | ||
23 | </my-help> | 31 | </my-help> |
24 | </form> \ No newline at end of file | 32 | </form> |
diff --git a/client/src/app/shared/users/user-notification.model.ts b/client/src/app/shared/users/user-notification.model.ts index 06eace71c..c3f4bf429 100644 --- a/client/src/app/shared/users/user-notification.model.ts +++ b/client/src/app/shared/users/user-notification.model.ts | |||
@@ -42,9 +42,10 @@ export class UserNotification implements UserNotificationServer { | |||
42 | state: FollowState | 42 | state: FollowState |
43 | follower: ActorInfo & { avatarUrl?: string } | 43 | follower: ActorInfo & { avatarUrl?: string } |
44 | following: { | 44 | following: { |
45 | type: 'account' | 'channel' | 45 | type: 'account' | 'channel' | 'instance' |
46 | name: string | 46 | name: string |
47 | displayName: string | 47 | displayName: string |
48 | host: string | ||
48 | } | 49 | } |
49 | } | 50 | } |
50 | 51 | ||
@@ -112,7 +113,10 @@ export class UserNotification implements UserNotificationServer { | |||
112 | 113 | ||
113 | case UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS: | 114 | case UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS: |
114 | this.videoAutoBlacklistUrl = '/admin/moderation/video-auto-blacklist/list' | 115 | this.videoAutoBlacklistUrl = '/admin/moderation/video-auto-blacklist/list' |
115 | this.videoUrl = this.buildVideoUrl(this.video) | 116 | // Backward compatibility where we did not assign videoBlacklist to this type of notification before |
117 | if (!this.videoBlacklist) this.videoBlacklist = { id: null, video: this.video } | ||
118 | |||
119 | this.videoUrl = this.buildVideoUrl(this.videoBlacklist.video) | ||
116 | break | 120 | break |
117 | 121 | ||
118 | case UserNotificationType.BLACKLIST_ON_MY_VIDEO: | 122 | case UserNotificationType.BLACKLIST_ON_MY_VIDEO: |
@@ -146,6 +150,10 @@ export class UserNotification implements UserNotificationServer { | |||
146 | case UserNotificationType.NEW_INSTANCE_FOLLOWER: | 150 | case UserNotificationType.NEW_INSTANCE_FOLLOWER: |
147 | this.instanceFollowUrl = '/admin/follows/followers-list' | 151 | this.instanceFollowUrl = '/admin/follows/followers-list' |
148 | break | 152 | break |
153 | |||
154 | case UserNotificationType.AUTO_INSTANCE_FOLLOWING: | ||
155 | this.instanceFollowUrl = '/admin/follows/following-list' | ||
156 | break | ||
149 | } | 157 | } |
150 | } catch (err) { | 158 | } catch (err) { |
151 | this.type = null | 159 | this.type = null |
diff --git a/client/src/app/shared/users/user-notifications.component.html b/client/src/app/shared/users/user-notifications.component.html index 292813426..a0f8e6df5 100644 --- a/client/src/app/shared/users/user-notifications.component.html +++ b/client/src/app/shared/users/user-notifications.component.html | |||
@@ -8,7 +8,7 @@ | |||
8 | <img alt="" aria-labelledby="avatar" class="avatar" [src]="notification.video.channel.avatarUrl" /> | 8 | <img alt="" aria-labelledby="avatar" class="avatar" [src]="notification.video.channel.avatarUrl" /> |
9 | 9 | ||
10 | <div class="message"> | 10 | <div class="message"> |
11 | {{ notification.video.channel.displayName }} published a <a (click)="markAsRead(notification)" [routerLink]="notification.videoUrl">new video</a> | 11 | {{ notification.video.channel.displayName }} published a new video: <a (click)="markAsRead(notification)" [routerLink]="notification.videoUrl">{{ notification.video.name }}</a> |
12 | </div> | 12 | </div> |
13 | </ng-container> | 13 | </ng-container> |
14 | 14 | ||
@@ -40,7 +40,7 @@ | |||
40 | <my-global-icon iconName="no"></my-global-icon> | 40 | <my-global-icon iconName="no"></my-global-icon> |
41 | 41 | ||
42 | <div class="message"> | 42 | <div class="message"> |
43 | The recently added video <a (click)="markAsRead(notification)" [routerLink]="notification.videoUrl">{{ notification.video.name }}</a> has been <a (click)="markAsRead(notification)" [routerLink]="notification.videoAutoBlacklistUrl">auto-blacklisted</a> | 43 | The recently added video <a (click)="markAsRead(notification)" [routerLink]="notification.videoUrl">{{ notification.videoBlacklist.video.name }}</a> has been <a (click)="markAsRead(notification)" [routerLink]="notification.videoAutoBlacklistUrl">auto-blacklisted</a> |
44 | </div> | 44 | </div> |
45 | </ng-container> | 45 | </ng-container> |
46 | 46 | ||
@@ -111,6 +111,14 @@ | |||
111 | <ng-container *ngIf="notification.actorFollow.state === 'pending'"> awaiting your approval</ng-container> | 111 | <ng-container *ngIf="notification.actorFollow.state === 'pending'"> awaiting your approval</ng-container> |
112 | </div> | 112 | </div> |
113 | </ng-container> | 113 | </ng-container> |
114 | |||
115 | <ng-container i18n *ngSwitchCase="UserNotificationType.AUTO_INSTANCE_FOLLOWING"> | ||
116 | <my-global-icon iconName="users"></my-global-icon> | ||
117 | |||
118 | <div class="message"> | ||
119 | Your instance automatically followed <a (click)="markAsRead(notification)" [routerLink]="notification.instanceFollowUrl">{{ notification.actorFollow.following.host }}</a> | ||
120 | </div> | ||
121 | </ng-container> | ||
114 | </ng-container> | 122 | </ng-container> |
115 | 123 | ||
116 | <div class="from-date">{{ notification.createdAt | myFromNow }}</div> | 124 | <div class="from-date">{{ notification.createdAt | myFromNow }}</div> |
diff --git a/client/src/app/shared/users/user.model.ts b/client/src/app/shared/users/user.model.ts index 53809f82c..656b73dd2 100644 --- a/client/src/app/shared/users/user.model.ts +++ b/client/src/app/shared/users/user.model.ts | |||
@@ -9,31 +9,38 @@ export class User implements UserServerModel { | |||
9 | username: string | 9 | username: string |
10 | email: string | 10 | email: string |
11 | pendingEmail: string | null | 11 | pendingEmail: string | null |
12 | |||
12 | emailVerified: boolean | 13 | emailVerified: boolean |
13 | nsfwPolicy: NSFWPolicyType | 14 | nsfwPolicy: NSFWPolicyType |
14 | 15 | ||
15 | role: UserRole | 16 | adminFlags?: UserAdminFlag |
16 | roleLabel: string | ||
17 | 17 | ||
18 | webTorrentEnabled: boolean | ||
19 | autoPlayVideo: boolean | 18 | autoPlayVideo: boolean |
19 | webTorrentEnabled: boolean | ||
20 | videosHistoryEnabled: boolean | 20 | videosHistoryEnabled: boolean |
21 | videoLanguages: string[] | 21 | videoLanguages: string[] |
22 | 22 | ||
23 | role: UserRole | ||
24 | roleLabel: string | ||
25 | |||
23 | videoQuota: number | 26 | videoQuota: number |
24 | videoQuotaDaily: number | 27 | videoQuotaDaily: number |
25 | account: Account | 28 | videoQuotaUsed?: number |
26 | videoChannels: VideoChannel[] | 29 | videoQuotaUsedDaily?: number |
27 | createdAt: Date | ||
28 | 30 | ||
29 | theme: string | 31 | theme: string |
30 | 32 | ||
31 | adminFlags?: UserAdminFlag | 33 | account: Account |
34 | notificationSettings?: UserNotificationSetting | ||
35 | videoChannels?: VideoChannel[] | ||
32 | 36 | ||
33 | blocked: boolean | 37 | blocked: boolean |
34 | blockedReason?: string | 38 | blockedReason?: string |
35 | 39 | ||
36 | notificationSettings?: UserNotificationSetting | 40 | noInstanceConfigWarningModal: boolean |
41 | noWelcomeModal: boolean | ||
42 | |||
43 | createdAt: Date | ||
37 | 44 | ||
38 | constructor (hash: Partial<UserServerModel>) { | 45 | constructor (hash: Partial<UserServerModel>) { |
39 | this.id = hash.id | 46 | this.id = hash.id |
@@ -43,13 +50,16 @@ export class User implements UserServerModel { | |||
43 | this.role = hash.role | 50 | this.role = hash.role |
44 | 51 | ||
45 | this.videoChannels = hash.videoChannels | 52 | this.videoChannels = hash.videoChannels |
53 | |||
46 | this.videoQuota = hash.videoQuota | 54 | this.videoQuota = hash.videoQuota |
47 | this.videoQuotaDaily = hash.videoQuotaDaily | 55 | this.videoQuotaDaily = hash.videoQuotaDaily |
56 | this.videoQuotaUsed = hash.videoQuotaUsed | ||
57 | this.videoQuotaUsedDaily = hash.videoQuotaUsedDaily | ||
58 | |||
48 | this.nsfwPolicy = hash.nsfwPolicy | 59 | this.nsfwPolicy = hash.nsfwPolicy |
49 | this.webTorrentEnabled = hash.webTorrentEnabled | 60 | this.webTorrentEnabled = hash.webTorrentEnabled |
50 | this.videosHistoryEnabled = hash.videosHistoryEnabled | 61 | this.videosHistoryEnabled = hash.videosHistoryEnabled |
51 | this.autoPlayVideo = hash.autoPlayVideo | 62 | this.autoPlayVideo = hash.autoPlayVideo |
52 | this.createdAt = hash.createdAt | ||
53 | 63 | ||
54 | this.theme = hash.theme | 64 | this.theme = hash.theme |
55 | 65 | ||
@@ -58,8 +68,13 @@ export class User implements UserServerModel { | |||
58 | this.blocked = hash.blocked | 68 | this.blocked = hash.blocked |
59 | this.blockedReason = hash.blockedReason | 69 | this.blockedReason = hash.blockedReason |
60 | 70 | ||
71 | this.noInstanceConfigWarningModal = hash.noInstanceConfigWarningModal | ||
72 | this.noWelcomeModal = hash.noWelcomeModal | ||
73 | |||
61 | this.notificationSettings = hash.notificationSettings | 74 | this.notificationSettings = hash.notificationSettings |
62 | 75 | ||
76 | this.createdAt = hash.createdAt | ||
77 | |||
63 | if (hash.account !== undefined) { | 78 | if (hash.account !== undefined) { |
64 | this.account = new Account(hash.account) | 79 | this.account = new Account(hash.account) |
65 | } | 80 | } |
diff --git a/client/src/app/shared/video/videos-selection.component.ts b/client/src/app/shared/video/videos-selection.component.ts index 994e0fa1e..064420056 100644 --- a/client/src/app/shared/video/videos-selection.component.ts +++ b/client/src/app/shared/video/videos-selection.component.ts | |||
@@ -35,7 +35,7 @@ export class VideosSelectionComponent extends AbstractVideoList implements OnIni | |||
35 | @Input() titlePage: string | 35 | @Input() titlePage: string |
36 | @Input() miniatureDisplayOptions: MiniatureDisplayOptions | 36 | @Input() miniatureDisplayOptions: MiniatureDisplayOptions |
37 | @Input() getVideosObservableFunction: (page: number, sort?: VideoSortField) => Observable<ResultList<Video>> | 37 | @Input() getVideosObservableFunction: (page: number, sort?: VideoSortField) => Observable<ResultList<Video>> |
38 | @ContentChildren(PeerTubeTemplateDirective) templates: QueryList<PeerTubeTemplateDirective> | 38 | @ContentChildren(PeerTubeTemplateDirective) templates: QueryList<PeerTubeTemplateDirective<'rowButtons' | 'globalButtons'>> |
39 | 39 | ||
40 | @Output() selectionChange = new EventEmitter<SelectionType>() | 40 | @Output() selectionChange = new EventEmitter<SelectionType>() |
41 | @Output() videosModelChange = new EventEmitter<Video[]>() | 41 | @Output() videosModelChange = new EventEmitter<Video[]>() |
diff --git a/client/src/app/videos/+video-edit/shared/video-edit.component.html b/client/src/app/videos/+video-edit/shared/video-edit.component.html index 217cadc66..245ae42b6 100644 --- a/client/src/app/videos/+video-edit/shared/video-edit.component.html +++ b/client/src/app/videos/+video-edit/shared/video-edit.component.html | |||
@@ -15,7 +15,16 @@ | |||
15 | 15 | ||
16 | <div class="form-group"> | 16 | <div class="form-group"> |
17 | <label i18n class="label-tags">Tags</label> | 17 | <label i18n class="label-tags">Tags</label> |
18 | <my-help i18n-preHtml preHtml="Tags could be used to suggest relevant recommendations.</br>Press Enter to add a new tag."></my-help> | 18 | |
19 | <my-help> | ||
20 | <ng-template ptTemplate="customHtml"> | ||
21 | <ng-container i18n> | ||
22 | Tags could be used to suggest relevant recommendations. <br /> | ||
23 | Press Enter to add a new tag. | ||
24 | </ng-container> | ||
25 | </ng-template> | ||
26 | </my-help> | ||
27 | |||
19 | <tag-input | 28 | <tag-input |
20 | [validators]="tagValidators" [errorMessages]="tagValidatorsMessages" | 29 | [validators]="tagValidators" [errorMessages]="tagValidatorsMessages" |
21 | i18n-placeholder placeholder="+ Tag" i18n-secondaryPlaceholder secondaryPlaceholder="Enter a new tag" | 30 | i18n-placeholder placeholder="+ Tag" i18n-secondaryPlaceholder secondaryPlaceholder="Enter a new tag" |
@@ -25,7 +34,15 @@ | |||
25 | 34 | ||
26 | <div class="form-group"> | 35 | <div class="form-group"> |
27 | <label i18n for="description">Description</label> | 36 | <label i18n for="description">Description</label> |
28 | <my-help helpType="markdownText" i18n-preHtml preHtml="Video descriptions are truncated by default and require manual action to expand them."></my-help> | 37 | |
38 | <my-help helpType="markdownText"> | ||
39 | <ng-template ptTemplate="preHtml"> | ||
40 | <ng-container i18n> | ||
41 | Video descriptions are truncated by default and require manual action to expand them. | ||
42 | </ng-container> | ||
43 | </ng-template> | ||
44 | </my-help> | ||
45 | |||
29 | <my-markdown-textarea truncate="250" formControlName="description"></my-markdown-textarea> | 46 | <my-markdown-textarea truncate="250" formControlName="description"></my-markdown-textarea> |
30 | 47 | ||
31 | <div *ngIf="formErrors.description" class="form-error"> | 48 | <div *ngIf="formErrors.description" class="form-error"> |
@@ -114,20 +131,25 @@ | |||
114 | </div> | 131 | </div> |
115 | </div> | 132 | </div> |
116 | 133 | ||
117 | <my-peertube-checkbox | 134 | <my-peertube-checkbox inputName="nsfw" formControlName="nsfw" helpPlacement="bottom-right"> |
118 | inputName="nsfw" formControlName="nsfw" | 135 | <ng-template ptTemplate="label"> |
119 | i18n-labelText labelText="This video contains mature or explicit content" | 136 | <ng-container i18n>This video contains mature or explicit content</ng-container> |
120 | i18n-helpHtml helpHtml="Some instances do not list videos containing mature or explicit content by default." | 137 | </ng-template> |
121 | helpPlacement="bottom-right" | 138 | |
122 | ></my-peertube-checkbox> | 139 | <ng-template ptTemplate="help"> |
123 | 140 | <ng-container i18n>Some instances do not list videos containing mature or explicit content by default.</ng-container> | |
124 | <my-peertube-checkbox | 141 | </ng-template> |
125 | *ngIf="waitTranscodingEnabled" | 142 | </my-peertube-checkbox> |
126 | inputName="waitTranscoding" formControlName="waitTranscoding" | 143 | |
127 | i18n-labelText labelText="Wait transcoding before publishing the video" | 144 | <my-peertube-checkbox *ngIf="waitTranscodingEnabled" inputName="waitTranscoding" formControlName="waitTranscoding" helpPlacement="bottom-right"> |
128 | i18n-helpHtml helpHtml="If you decide not to wait for transcoding before publishing the video, it could be unplayable until transcoding ends." | 145 | <ng-template ptTemplate="label"> |
129 | helpPlacement="bottom-right" | 146 | <ng-container i18n>Wait transcoding before publishing the video</ng-container> |
130 | ></my-peertube-checkbox> | 147 | </ng-template> |
148 | |||
149 | <ng-template ptTemplate="help"> | ||
150 | <ng-container i18n>If you decide not to wait for transcoding before publishing the video, it could be unplayable until transcoding ends.</ng-container> | ||
151 | </ng-template> | ||
152 | </my-peertube-checkbox> | ||
131 | 153 | ||
132 | </div> | 154 | </div> |
133 | </div> | 155 | </div> |
diff --git a/client/src/app/videos/+video-edit/video-add-components/video-import-torrent.component.html b/client/src/app/videos/+video-edit/video-add-components/video-import-torrent.component.html index 7a495fea5..c290fd4b1 100644 --- a/client/src/app/videos/+video-edit/video-add-components/video-import-torrent.component.html +++ b/client/src/app/videos/+video-edit/video-add-components/video-import-torrent.component.html | |||
@@ -12,10 +12,14 @@ | |||
12 | 12 | ||
13 | <div class="form-group form-group-magnet-uri"> | 13 | <div class="form-group form-group-magnet-uri"> |
14 | <label i18n for="magnetUri">Paste magnet URI</label> | 14 | <label i18n for="magnetUri">Paste magnet URI</label> |
15 | <my-help | 15 | <my-help> |
16 | helpType="custom" i18n-customHtml | 16 | <ng-template ptTemplate="customHtml"> |
17 | customHtml="You can import any torrent file that points to a mp4 file. You should make sure you have diffusion rights over the content it points to, otherwise it could cause legal trouble to yourself and your instance." | 17 | <ng-container i18n> |
18 | ></my-help> | 18 | You can import any torrent file that points to a mp4 file. |
19 | You should make sure you have diffusion rights over the content it points to, otherwise it could cause legal trouble to yourself and your instance. | ||
20 | </ng-container> | ||
21 | </ng-template> | ||
22 | </my-help> | ||
19 | 23 | ||
20 | <input type="text" id="magnetUri" [(ngModel)]="magnetUri" /> | 24 | <input type="text" id="magnetUri" [(ngModel)]="magnetUri" /> |
21 | </div> | 25 | </div> |
diff --git a/client/src/app/videos/+video-edit/video-add-components/video-import-url.component.html b/client/src/app/videos/+video-edit/video-add-components/video-import-url.component.html index e4f19faa8..09d0b8272 100644 --- a/client/src/app/videos/+video-edit/video-add-components/video-import-url.component.html +++ b/client/src/app/videos/+video-edit/video-add-components/video-import-url.component.html | |||
@@ -4,10 +4,16 @@ | |||
4 | 4 | ||
5 | <div class="form-group"> | 5 | <div class="form-group"> |
6 | <label i18n for="targetUrl">URL</label> | 6 | <label i18n for="targetUrl">URL</label> |
7 | <my-help | 7 | |
8 | helpType="custom" i18n-customHtml | 8 | <my-help> |
9 | customHtml="You can import any URL <a href='https://rg3.github.io/youtube-dl/supportedsites.html' target='_blank' rel='noopener noreferrer'>supported by youtube-dl</a> or URL that points to a raw MP4 file. You should make sure you have diffusion rights over the content it points to, otherwise it could cause legal trouble to yourself and your instance." | 9 | <ng-template ptTemplate="customHtml"> |
10 | ></my-help> | 10 | <ng-container i18n> |
11 | You can import any URL <a href='https://rg3.github.io/youtube-dl/supportedsites.html' target='_blank' rel='noopener noreferrer'>supported by youtube-dl</a> | ||
12 | or URL that points to a raw MP4 file. | ||
13 | You should make sure you have diffusion rights over the content it points to, otherwise it could cause legal trouble to yourself and your instance. | ||
14 | </ng-container> | ||
15 | </ng-template> | ||
16 | </my-help> | ||
11 | 17 | ||
12 | <input type="text" id="targetUrl" [(ngModel)]="targetUrl" /> | 18 | <input type="text" id="targetUrl" [(ngModel)]="targetUrl" /> |
13 | </div> | 19 | </div> |
diff --git a/client/src/assets/images/framasoft.png b/client/src/assets/images/framasoft.png new file mode 100644 index 000000000..57be8c219 --- /dev/null +++ b/client/src/assets/images/framasoft.png | |||
Binary files differ | |||
diff --git a/client/src/assets/player/p2p-media-loader/p2p-media-loader-plugin.ts b/client/src/assets/player/p2p-media-loader/p2p-media-loader-plugin.ts index 0c8c612ee..c44c184d5 100644 --- a/client/src/assets/player/p2p-media-loader/p2p-media-loader-plugin.ts +++ b/client/src/assets/player/p2p-media-loader/p2p-media-loader-plugin.ts | |||
@@ -92,7 +92,7 @@ class P2pMediaLoaderPlugin extends Plugin { | |||
92 | this.p2pEngine.on(Events.SegmentError, (segment: Segment, err) => { | 92 | this.p2pEngine.on(Events.SegmentError, (segment: Segment, err) => { |
93 | console.error('Segment error.', segment, err) | 93 | console.error('Segment error.', segment, err) |
94 | 94 | ||
95 | this.options.redundancyUrlManager.removeByOriginUrl(segment.url) | 95 | this.options.redundancyUrlManager.removeBySegmentUrl(segment.requestUrl) |
96 | }) | 96 | }) |
97 | 97 | ||
98 | this.statsP2PBytes.numPeers = 1 + this.options.redundancyUrlManager.countBaseUrls() | 98 | this.statsP2PBytes.numPeers = 1 + this.options.redundancyUrlManager.countBaseUrls() |
diff --git a/client/src/assets/player/p2p-media-loader/redundancy-url-manager.ts b/client/src/assets/player/p2p-media-loader/redundancy-url-manager.ts index 7fc2b6ab1..abab8aa99 100644 --- a/client/src/assets/player/p2p-media-loader/redundancy-url-manager.ts +++ b/client/src/assets/player/p2p-media-loader/redundancy-url-manager.ts | |||
@@ -2,9 +2,6 @@ import { basename, dirname } from 'path' | |||
2 | 2 | ||
3 | class RedundancyUrlManager { | 3 | class RedundancyUrlManager { |
4 | 4 | ||
5 | // Remember by what new URL we replaced an origin URL | ||
6 | private replacedSegmentUrls: { [originUrl: string]: string } = {} | ||
7 | |||
8 | constructor (private baseUrls: string[] = []) { | 5 | constructor (private baseUrls: string[] = []) { |
9 | // empty | 6 | // empty |
10 | } | 7 | } |
@@ -17,16 +14,7 @@ class RedundancyUrlManager { | |||
17 | this.baseUrls = this.baseUrls.filter(u => u !== baseUrl && u !== baseUrl + '/') | 14 | this.baseUrls = this.baseUrls.filter(u => u !== baseUrl && u !== baseUrl + '/') |
18 | } | 15 | } |
19 | 16 | ||
20 | removeByOriginUrl (originUrl: string) { | ||
21 | const replaced = this.replacedSegmentUrls[originUrl] | ||
22 | if (!replaced) return | ||
23 | |||
24 | return this.removeBySegmentUrl(replaced) | ||
25 | } | ||
26 | |||
27 | buildUrl (url: string) { | 17 | buildUrl (url: string) { |
28 | delete this.replacedSegmentUrls[url] | ||
29 | |||
30 | const max = this.baseUrls.length + 1 | 18 | const max = this.baseUrls.length + 1 |
31 | const i = this.getRandomInt(max) | 19 | const i = this.getRandomInt(max) |
32 | 20 | ||
@@ -35,10 +23,7 @@ class RedundancyUrlManager { | |||
35 | const newBaseUrl = this.baseUrls[i] | 23 | const newBaseUrl = this.baseUrls[i] |
36 | const slashPart = newBaseUrl.endsWith('/') ? '' : '/' | 24 | const slashPart = newBaseUrl.endsWith('/') ? '' : '/' |
37 | 25 | ||
38 | const newUrl = newBaseUrl + slashPart + basename(url) | 26 | return newBaseUrl + slashPart + basename(url) |
39 | this.replacedSegmentUrls[url] = newUrl | ||
40 | |||
41 | return newUrl | ||
42 | } | 27 | } |
43 | 28 | ||
44 | countBaseUrls () { | 29 | countBaseUrls () { |
diff --git a/client/src/sass/include/_mixins.scss b/client/src/sass/include/_mixins.scss index abbc137b2..26ba490c7 100644 --- a/client/src/sass/include/_mixins.scss +++ b/client/src/sass/include/_mixins.scss | |||
@@ -343,6 +343,7 @@ | |||
343 | & + span { | 343 | & + span { |
344 | position: relative; | 344 | position: relative; |
345 | width: 18px; | 345 | width: 18px; |
346 | min-width: 18px; | ||
346 | height: 18px; | 347 | height: 18px; |
347 | border: $border-width solid var(--mainForegroundColor); | 348 | border: $border-width solid var(--mainForegroundColor); |
348 | border-radius: 3px; | 349 | border-radius: 3px; |
@@ -395,6 +396,7 @@ | |||
395 | border-radius: 50%; | 396 | border-radius: 50%; |
396 | width: $size; | 397 | width: $size; |
397 | height: $size; | 398 | height: $size; |
399 | min-width: $size; | ||
398 | } | 400 | } |
399 | 401 | ||
400 | @mixin chevron ($size, $border-width) { | 402 | @mixin chevron ($size, $border-width) { |
diff --git a/client/src/sass/player/peertube-skin.scss b/client/src/sass/player/peertube-skin.scss index 1a5144b11..4bf48a570 100644 --- a/client/src/sass/player/peertube-skin.scss +++ b/client/src/sass/player/peertube-skin.scss | |||
@@ -26,11 +26,6 @@ body { | |||
26 | .vjs-dock-description { | 26 | .vjs-dock-description { |
27 | font-size: 11px; | 27 | font-size: 11px; |
28 | 28 | ||
29 | .text::before, .text::after { | ||
30 | display: inline-block; | ||
31 | content: '\1F308'; | ||
32 | } | ||
33 | |||
34 | .text::before { | 29 | .text::before { |
35 | margin-right: 4px; | 30 | margin-right: 4px; |
36 | } | 31 | } |
diff --git a/client/src/standalone/videos/embed.ts b/client/src/standalone/videos/embed.ts index 6ff3efef1..19d2a1d02 100644 --- a/client/src/standalone/videos/embed.ts +++ b/client/src/standalone/videos/embed.ts | |||
@@ -239,7 +239,7 @@ export class PeerTubeEmbed { | |||
239 | 239 | ||
240 | const config: ServerConfig = await configResponse.json() | 240 | const config: ServerConfig = await configResponse.json() |
241 | const description = config.tracker.enabled && this.warningTitle | 241 | const description = config.tracker.enabled && this.warningTitle |
242 | ? '<span class="text">' + this.player.localize('Uses P2P, others may know your IP is downloading this video.') + '</span>' | 242 | ? '<span class="text">' + this.player.localize('Watching this video may reveal your IP address to others.') + '</span>' |
243 | : undefined | 243 | : undefined |
244 | 244 | ||
245 | this.player.dock({ | 245 | this.player.dock({ |
diff --git a/client/yarn.lock b/client/yarn.lock index a67ffe6d1..6755d7e64 100644 --- a/client/yarn.lock +++ b/client/yarn.lock | |||
@@ -1139,6 +1139,11 @@ async-foreach@^0.1.3: | |||
1139 | resolved "https://registry.yarnpkg.com/async-foreach/-/async-foreach-0.1.3.tgz#36121f845c0578172de419a97dbeb1d16ec34542" | 1139 | resolved "https://registry.yarnpkg.com/async-foreach/-/async-foreach-0.1.3.tgz#36121f845c0578172de419a97dbeb1d16ec34542" |
1140 | integrity sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI= | 1140 | integrity sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI= |
1141 | 1141 | ||
1142 | async-limiter@^1.0.0: | ||
1143 | version "1.0.1" | ||
1144 | resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" | ||
1145 | integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== | ||
1146 | |||
1142 | async-limiter@~1.0.0: | 1147 | async-limiter@~1.0.0: |
1143 | version "1.0.0" | 1148 | version "1.0.0" |
1144 | resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8" | 1149 | resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8" |
@@ -1433,7 +1438,7 @@ bittorrent-protocol@^3.0.0: | |||
1433 | speedometer "^1.0.0" | 1438 | speedometer "^1.0.0" |
1434 | unordered-array-remove "^1.0.2" | 1439 | unordered-array-remove "^1.0.2" |
1435 | 1440 | ||
1436 | bittorrent-tracker@^9.0.0, bittorrent-tracker@^9.11.0: | 1441 | bittorrent-tracker@^9.0.0: |
1437 | version "9.11.0" | 1442 | version "9.11.0" |
1438 | resolved "https://registry.yarnpkg.com/bittorrent-tracker/-/bittorrent-tracker-9.11.0.tgz#9911f9c14e5a29f84990a0c31b3d83dd16eb2876" | 1443 | resolved "https://registry.yarnpkg.com/bittorrent-tracker/-/bittorrent-tracker-9.11.0.tgz#9911f9c14e5a29f84990a0c31b3d83dd16eb2876" |
1439 | integrity sha512-T1zvW/kSeEnWT4I3JE+6c7aZbO5jtleZyQe911SyzIxFF9DvtUNWXud3p5ZUkXaoI2xXwfpvlks5VFj5SKEB+A== | 1444 | integrity sha512-T1zvW/kSeEnWT4I3JE+6c7aZbO5jtleZyQe911SyzIxFF9DvtUNWXud3p5ZUkXaoI2xXwfpvlks5VFj5SKEB+A== |
@@ -1463,6 +1468,36 @@ bittorrent-tracker@^9.0.0, bittorrent-tracker@^9.11.0: | |||
1463 | bufferutil "^4.0.0" | 1468 | bufferutil "^4.0.0" |
1464 | utf-8-validate "^5.0.1" | 1469 | utf-8-validate "^5.0.1" |
1465 | 1470 | ||
1471 | bittorrent-tracker@^9.14.4: | ||
1472 | version "9.14.4" | ||
1473 | resolved "https://registry.yarnpkg.com/bittorrent-tracker/-/bittorrent-tracker-9.14.4.tgz#0d9661560e6fec37689dfc5045142772eac05536" | ||
1474 | integrity sha512-2Y/MNRjYhysD6t4r38z7l1WTT7g23IAqRWZRsj7xnnpciFn4xE4qiKmyFwA4gtbFGAZ14K3DdaqZbiQsC3PEfQ== | ||
1475 | dependencies: | ||
1476 | bencode "^2.0.0" | ||
1477 | bittorrent-peerid "^1.0.2" | ||
1478 | bn.js "^5.0.0" | ||
1479 | chrome-dgram "^3.0.2" | ||
1480 | compact2string "^1.2.0" | ||
1481 | debug "^4.0.1" | ||
1482 | ip "^1.0.1" | ||
1483 | lru "^3.0.0" | ||
1484 | minimist "^1.1.1" | ||
1485 | once "^1.3.0" | ||
1486 | random-iterate "^1.0.1" | ||
1487 | randombytes "^2.0.3" | ||
1488 | run-parallel "^1.1.2" | ||
1489 | run-series "^1.0.2" | ||
1490 | simple-get "^3.0.0" | ||
1491 | simple-peer "^9.0.0" | ||
1492 | simple-websocket "^8.0.0" | ||
1493 | string2compact "^1.1.1" | ||
1494 | uniq "^1.0.1" | ||
1495 | unordered-array-remove "^1.0.2" | ||
1496 | ws "^7.0.0" | ||
1497 | optionalDependencies: | ||
1498 | bufferutil "^4.0.0" | ||
1499 | utf-8-validate "^5.0.1" | ||
1500 | |||
1466 | blob-to-buffer@^1.2.6: | 1501 | blob-to-buffer@^1.2.6: |
1467 | version "1.2.8" | 1502 | version "1.2.8" |
1468 | resolved "https://registry.yarnpkg.com/blob-to-buffer/-/blob-to-buffer-1.2.8.tgz#78eeeb332f1280ed0ca6fb2b60693a8c6d36903a" | 1503 | resolved "https://registry.yarnpkg.com/blob-to-buffer/-/blob-to-buffer-1.2.8.tgz#78eeeb332f1280ed0ca6fb2b60693a8c6d36903a" |
@@ -1506,6 +1541,11 @@ bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: | |||
1506 | resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" | 1541 | resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" |
1507 | integrity sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA== | 1542 | integrity sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA== |
1508 | 1543 | ||
1544 | bn.js@^5.0.0: | ||
1545 | version "5.0.0" | ||
1546 | resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.0.0.tgz#5c3d398021b3ddb548c1296a16f857e908f35c70" | ||
1547 | integrity sha512-bVwDX8AF+72fIUNuARelKAlQUNtPOfG2fRxorbVvFk4zpHbqLrPdOGfVg5vrKwVzLLePqPBiATaOZNELQzmS0A== | ||
1548 | |||
1509 | body-parser@1.19.0, body-parser@^1.16.1: | 1549 | body-parser@1.19.0, body-parser@^1.16.1: |
1510 | version "1.19.0" | 1550 | version "1.19.0" |
1511 | resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" | 1551 | resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" |
@@ -1959,6 +1999,14 @@ chownr@^1.1.1: | |||
1959 | resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.2.tgz#a18f1e0b269c8a6a5d3c86eb298beb14c3dd7bf6" | 1999 | resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.2.tgz#a18f1e0b269c8a6a5d3c86eb298beb14c3dd7bf6" |
1960 | integrity sha512-GkfeAQh+QNy3wquu9oIZr6SS5x7wGdSgNQvD10X3r+AZr1Oys22HW8kAmDMvNg2+Dm0TeGaEuO8gFwdBXxwO8A== | 2000 | integrity sha512-GkfeAQh+QNy3wquu9oIZr6SS5x7wGdSgNQvD10X3r+AZr1Oys22HW8kAmDMvNg2+Dm0TeGaEuO8gFwdBXxwO8A== |
1961 | 2001 | ||
2002 | chrome-dgram@^3.0.2: | ||
2003 | version "3.0.2" | ||
2004 | resolved "https://registry.yarnpkg.com/chrome-dgram/-/chrome-dgram-3.0.2.tgz#7e0e00084b57971714214372368ad18a7785ad52" | ||
2005 | integrity sha512-Ay741EHF/Ib18un+LUtBNK43NrabD6GOuwVaka7uUbV0gFRLEPULm2Q05YSzRNBtSrbaO4eErmDdniiy/u8Lig== | ||
2006 | dependencies: | ||
2007 | inherits "^2.0.1" | ||
2008 | run-series "^1.1.2" | ||
2009 | |||
1962 | chrome-trace-event@^1.0.0: | 2010 | chrome-trace-event@^1.0.0: |
1963 | version "1.0.2" | 2011 | version "1.0.2" |
1964 | resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz#234090ee97c7d4ad1a2c4beae27505deffc608a4" | 2012 | resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz#234090ee97c7d4ad1a2c4beae27505deffc608a4" |
@@ -6488,26 +6536,26 @@ p-try@^2.0.0: | |||
6488 | resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" | 6536 | resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" |
6489 | integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== | 6537 | integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== |
6490 | 6538 | ||
6491 | p2p-media-loader-core@^0.6.1: | 6539 | p2p-media-loader-core@^0.6.2: |
6492 | version "0.6.1" | 6540 | version "0.6.2" |
6493 | resolved "https://registry.yarnpkg.com/p2p-media-loader-core/-/p2p-media-loader-core-0.6.1.tgz#90cc05460cb5207897953e92059b32930f06a56f" | 6541 | resolved "https://registry.yarnpkg.com/p2p-media-loader-core/-/p2p-media-loader-core-0.6.2.tgz#7e46cf8fc4357596f389e106bee850908cc974ef" |
6494 | integrity sha512-bTyOdTVxbjzr1GCt6bOIxXlw7U6gPvYXOGo07EU0wufabKscn/TNyuTH4fDhVtw6NGMISn18G06td3V049tOBw== | 6542 | integrity sha512-yspgCOrVVYitVNece5CA6W/kcVA0UybvbD4kyBE5ooyhCAXQK5/q6JsIpXiVQ3VkQw8Qs4mfZjU39Vt6vEk6aw== |
6495 | dependencies: | 6543 | dependencies: |
6496 | bittorrent-tracker "^9.11.0" | 6544 | bittorrent-tracker "^9.14.4" |
6497 | debug "^4.1.1" | 6545 | debug "^4.1.1" |
6498 | events "^3.0.0" | 6546 | events "^3.0.0" |
6499 | get-browser-rtc "^1.0.2" | 6547 | get-browser-rtc "^1.0.2" |
6500 | sha.js "^2.4.11" | 6548 | sha.js "^2.4.11" |
6501 | simple-peer "^9.4.0" | 6549 | simple-peer "^9.5.0" |
6502 | 6550 | ||
6503 | p2p-media-loader-hlsjs@^0.6.1: | 6551 | p2p-media-loader-hlsjs@^0.6.2: |
6504 | version "0.6.1" | 6552 | version "0.6.2" |
6505 | resolved "https://registry.yarnpkg.com/p2p-media-loader-hlsjs/-/p2p-media-loader-hlsjs-0.6.1.tgz#558e1737241f3c17810cddafde0e992c20656886" | 6553 | resolved "https://registry.yarnpkg.com/p2p-media-loader-hlsjs/-/p2p-media-loader-hlsjs-0.6.2.tgz#b66f977a5d28986c8f6e62d2ffa297aec3c05186" |
6506 | integrity sha512-JadTwrxNNKXyO4MyiK7i5zT1zOSFmaiIOlE4Gr6NjxDg8v3+Q8q09YHJPXumXexUWDNpw5vw8eHTpBdQClJ9lQ== | 6554 | integrity sha512-5LgqWPDsgyST9rxoHGDpExZU1rIDZIT0qft2wAnlg8Cb8aVeaBxUsmF4Sj692Qb5/GBDsi8vLE03LW8gpvlh1g== |
6507 | dependencies: | 6555 | dependencies: |
6508 | events "^3.0.0" | 6556 | events "^3.0.0" |
6509 | m3u8-parser "^4.4.0" | 6557 | m3u8-parser "^4.4.0" |
6510 | p2p-media-loader-core "^0.6.1" | 6558 | p2p-media-loader-core "^0.6.2" |
6511 | 6559 | ||
6512 | package-json-versionify@^1.0.2: | 6560 | package-json-versionify@^1.0.2: |
6513 | version "1.0.4" | 6561 | version "1.0.4" |
@@ -7624,7 +7672,7 @@ run-queue@^1.0.0, run-queue@^1.0.3: | |||
7624 | dependencies: | 7672 | dependencies: |
7625 | aproba "^1.1.1" | 7673 | aproba "^1.1.1" |
7626 | 7674 | ||
7627 | run-series@^1.0.2: | 7675 | run-series@^1.0.2, run-series@^1.1.2: |
7628 | version "1.1.8" | 7676 | version "1.1.8" |
7629 | resolved "https://registry.yarnpkg.com/run-series/-/run-series-1.1.8.tgz#2c4558f49221e01cd6371ff4e0a1e203e460fc36" | 7677 | resolved "https://registry.yarnpkg.com/run-series/-/run-series-1.1.8.tgz#2c4558f49221e01cd6371ff4e0a1e203e460fc36" |
7630 | integrity sha512-+GztYEPRpIsQoCSraWHDBs9WVy4eVME16zhOtDB4H9J4xN0XRhknnmLOl+4gRgZtu8dpp9N/utSPjKH/xmDzXg== | 7678 | integrity sha512-+GztYEPRpIsQoCSraWHDBs9WVy4eVME16zhOtDB4H9J4xN0XRhknnmLOl+4gRgZtu8dpp9N/utSPjKH/xmDzXg== |
@@ -7985,7 +8033,7 @@ simple-get@^2.8.1, simple-get@^3.0.0, simple-get@^3.0.1: | |||
7985 | once "^1.3.1" | 8033 | once "^1.3.1" |
7986 | simple-concat "^1.0.0" | 8034 | simple-concat "^1.0.0" |
7987 | 8035 | ||
7988 | simple-peer@^9.0.0, simple-peer@^9.4.0: | 8036 | simple-peer@^9.0.0: |
7989 | version "9.4.0" | 8037 | version "9.4.0" |
7990 | resolved "https://registry.yarnpkg.com/simple-peer/-/simple-peer-9.4.0.tgz#eb82ef1181e10ec0c014a94953e2eb278f3d9025" | 8038 | resolved "https://registry.yarnpkg.com/simple-peer/-/simple-peer-9.4.0.tgz#eb82ef1181e10ec0c014a94953e2eb278f3d9025" |
7991 | integrity sha512-8qF32uq6SSSVXoBq9g31uGqZYupwRD3Ta/QK9fV04U/IbnIS6mictLb8/kjFyLVa3JrD7QYyKrw3nvJJ+lNFDw== | 8039 | integrity sha512-8qF32uq6SSSVXoBq9g31uGqZYupwRD3Ta/QK9fV04U/IbnIS6mictLb8/kjFyLVa3JrD7QYyKrw3nvJJ+lNFDw== |
@@ -7996,6 +8044,17 @@ simple-peer@^9.0.0, simple-peer@^9.4.0: | |||
7996 | randombytes "^2.0.3" | 8044 | randombytes "^2.0.3" |
7997 | readable-stream "^2.3.4" | 8045 | readable-stream "^2.3.4" |
7998 | 8046 | ||
8047 | simple-peer@^9.5.0: | ||
8048 | version "9.5.0" | ||
8049 | resolved "https://registry.yarnpkg.com/simple-peer/-/simple-peer-9.5.0.tgz#67ba8bd4b54efc3acf19aceafdc118b27e24fcbc" | ||
8050 | integrity sha512-3tROq3nBo/CIZI8PWlXGbAxQIlQF6KQ/zcd4lQ2pAC4+rPiV7E721hI22nTO54uw/nzb2HKbvmDtZ4Wr173+vA== | ||
8051 | dependencies: | ||
8052 | debug "^4.0.1" | ||
8053 | get-browser-rtc "^1.0.0" | ||
8054 | inherits "^2.0.1" | ||
8055 | randombytes "^2.0.3" | ||
8056 | readable-stream "^3.4.0" | ||
8057 | |||
7999 | simple-sha1@^2.0.0, simple-sha1@^2.0.8, simple-sha1@^2.1.0: | 8058 | simple-sha1@^2.0.0, simple-sha1@^2.0.8, simple-sha1@^2.1.0: |
8000 | version "2.1.2" | 8059 | version "2.1.2" |
8001 | resolved "https://registry.yarnpkg.com/simple-sha1/-/simple-sha1-2.1.2.tgz#de40cbd5aae278fde8e3bb3250a35d74c67326b1" | 8060 | resolved "https://registry.yarnpkg.com/simple-sha1/-/simple-sha1-2.1.2.tgz#de40cbd5aae278fde8e3bb3250a35d74c67326b1" |
@@ -8014,6 +8073,16 @@ simple-websocket@^7.0.1: | |||
8014 | readable-stream "^2.0.5" | 8073 | readable-stream "^2.0.5" |
8015 | ws "^6.0.0" | 8074 | ws "^6.0.0" |
8016 | 8075 | ||
8076 | simple-websocket@^8.0.0: | ||
8077 | version "8.0.1" | ||
8078 | resolved "https://registry.yarnpkg.com/simple-websocket/-/simple-websocket-8.0.1.tgz#c28af779034b329d0cf1448a45fdd311d21fa289" | ||
8079 | integrity sha512-2QKSRjf+tqFXLVmOQjf95gHeKhuyx2k1ouDjtnE0uKCYw84HfN85HsXo+GmPH+2PIh5BQql++g2AIbHgGAZU4w== | ||
8080 | dependencies: | ||
8081 | debug "^4.1.1" | ||
8082 | randombytes "^2.0.3" | ||
8083 | readable-stream "^3.1.1" | ||
8084 | ws "^7.0.0" | ||
8085 | |||
8017 | slash@^1.0.0: | 8086 | slash@^1.0.0: |
8018 | version "1.0.0" | 8087 | version "1.0.0" |
8019 | resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" | 8088 | resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" |
@@ -9710,6 +9779,13 @@ ws@^6.0.0: | |||
9710 | dependencies: | 9779 | dependencies: |
9711 | async-limiter "~1.0.0" | 9780 | async-limiter "~1.0.0" |
9712 | 9781 | ||
9782 | ws@^7.0.0: | ||
9783 | version "7.1.2" | ||
9784 | resolved "https://registry.yarnpkg.com/ws/-/ws-7.1.2.tgz#c672d1629de8bb27a9699eb599be47aeeedd8f73" | ||
9785 | integrity sha512-gftXq3XI81cJCgkUiAVixA0raD9IVmXqsylCrjRygw4+UOOGzPoxnQ6r/CnVL9i+mDncJo94tSkyrtuuQVBmrg== | ||
9786 | dependencies: | ||
9787 | async-limiter "^1.0.0" | ||
9788 | |||
9713 | ws@~3.3.1: | 9789 | ws@~3.3.1: |
9714 | version "3.3.3" | 9790 | version "3.3.3" |
9715 | resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2" | 9791 | resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2" |
diff --git a/config/default.yaml b/config/default.yaml index dfba23f59..5ebfdeddb 100644 --- a/config/default.yaml +++ b/config/default.yaml | |||
@@ -69,7 +69,7 @@ email: | |||
69 | 69 | ||
70 | # From the project root directory | 70 | # From the project root directory |
71 | storage: | 71 | storage: |
72 | tmp: 'storage/tmp/' # Used to download data (imports etc), store uploaded files before processing... | 72 | tmp: 'storage/tmp/' # Use to download data (imports etc), store uploaded files before processing... |
73 | avatars: 'storage/avatars/' | 73 | avatars: 'storage/avatars/' |
74 | videos: 'storage/videos/' | 74 | videos: 'storage/videos/' |
75 | streaming_playlists: 'storage/streaming-playlists/' | 75 | streaming_playlists: 'storage/streaming-playlists/' |
@@ -85,7 +85,7 @@ storage: | |||
85 | log: | 85 | log: |
86 | level: 'info' # debug/info/warning/error | 86 | level: 'info' # debug/info/warning/error |
87 | rotation: | 87 | rotation: |
88 | enabled : true # Enabled by default, if disabled make sure that 'storage.logs' is pointing to a folder handled by logrotate | 88 | enabled : true |
89 | 89 | ||
90 | search: | 90 | search: |
91 | # Add ability to fetch remote videos/actors by their URI, that may not be federated with your instance | 91 | # Add ability to fetch remote videos/actors by their URI, that may not be federated with your instance |
@@ -238,7 +238,60 @@ instance: | |||
238 | short_description: 'PeerTube, a federated (ActivityPub) video streaming platform using P2P (BitTorrent) directly in the web browser with WebTorrent and Angular.' | 238 | short_description: 'PeerTube, a federated (ActivityPub) video streaming platform using P2P (BitTorrent) directly in the web browser with WebTorrent and Angular.' |
239 | description: 'Welcome to this PeerTube instance!' # Support markdown | 239 | description: 'Welcome to this PeerTube instance!' # Support markdown |
240 | terms: 'No terms for now.' # Support markdown | 240 | terms: 'No terms for now.' # Support markdown |
241 | code_of_conduct: '' # Supports markdown | ||
242 | |||
243 | # Who moderates the instance? What is the policy regarding NSFW videos? Political videos? etc | ||
244 | moderation_information: '' # Supports markdown | ||
245 | |||
246 | # Why did you create this instance? | ||
247 | creation_reason: '' | ||
248 | |||
249 | # Who is behind the instance? A single person? A non profit? | ||
250 | administrator: '' | ||
251 | |||
252 | # How long do you plan to maintain this instance? | ||
253 | maintenance_lifetime: '' | ||
254 | |||
255 | # How will you pay the PeerTube instance server? With you own funds? With users donations? Advertising? | ||
256 | business_model: '' | ||
257 | |||
258 | # If you want to explain on what type of hardware your PeerTube instance runs | ||
259 | # Example: "2 vCore, 2GB RAM..." | ||
260 | hardware_information: '' # Supports Markdown | ||
261 | |||
262 | # What are the main languages of your instance? To interact with your users for example | ||
263 | # Uncomment or add the languages you want | ||
264 | # List of supported languages: https://peertube.cpy.re/api/v1/videos/languages | ||
265 | languages: | ||
266 | # - en | ||
267 | # - es | ||
268 | # - fr | ||
269 | |||
270 | # You can specify the main categories of your instance (dedicated to music, gaming or politics etc) | ||
271 | # Uncomment or add the category ids you want | ||
272 | # List of supported categories: https://peertube.cpy.re/api/v1/videos/categories | ||
273 | categories: | ||
274 | # - 1 # Music | ||
275 | # - 2 # Films | ||
276 | # - 3 # Vehicles | ||
277 | # - 4 # Art | ||
278 | # - 5 # Sports | ||
279 | # - 6 # Travels | ||
280 | # - 7 # Gaming | ||
281 | # - 8 # People | ||
282 | # - 9 # Comedy | ||
283 | # - 10 # Entertainment | ||
284 | # - 11 # News & Politics | ||
285 | # - 12 # How To | ||
286 | # - 13 # Education | ||
287 | # - 14 # Activism | ||
288 | # - 15 # Science & Technology | ||
289 | # - 16 # Animals | ||
290 | # - 17 # Kids | ||
291 | # - 18 # Food | ||
292 | |||
241 | default_client_route: '/videos/trending' | 293 | default_client_route: '/videos/trending' |
294 | |||
242 | # Whether or not the instance is dedicated to NSFW content | 295 | # Whether or not the instance is dedicated to NSFW content |
243 | # Enabling it will allow other administrators to know that you are mainly federating sensitive content | 296 | # Enabling it will allow other administrators to know that you are mainly federating sensitive content |
244 | # Moreover, the NSFW checkbox on video upload will be automatically checked by default | 297 | # Moreover, the NSFW checkbox on video upload will be automatically checked by default |
@@ -246,6 +299,7 @@ instance: | |||
246 | # By default, "do_not_list" or "blur" or "display" NSFW videos | 299 | # By default, "do_not_list" or "blur" or "display" NSFW videos |
247 | # Could be overridden per user with a setting | 300 | # Could be overridden per user with a setting |
248 | default_nsfw_policy: 'do_not_list' | 301 | default_nsfw_policy: 'do_not_list' |
302 | |||
249 | customizations: | 303 | customizations: |
250 | javascript: '' # Directly your JavaScript code (without <script> tags). Will be eval at runtime | 304 | javascript: '' # Directly your JavaScript code (without <script> tags). Will be eval at runtime |
251 | css: '' # Directly your CSS code (without <style> tags). Will be injected at runtime | 305 | css: '' # Directly your CSS code (without <style> tags). Will be injected at runtime |
@@ -273,5 +327,21 @@ followers: | |||
273 | # Whether or not an administrator must manually validate a new follower | 327 | # Whether or not an administrator must manually validate a new follower |
274 | manual_approval: false | 328 | manual_approval: false |
275 | 329 | ||
330 | followings: | ||
331 | instance: | ||
332 | # If you want to automatically follow back new instance followers | ||
333 | # Only follows accepted followers (in case you enabled manual followers approbation) | ||
334 | # If this option is enabled, use the mute feature instead of deleting followings | ||
335 | # /!\ Don't enable this if you don't have a reactive moderation team /!\ | ||
336 | auto_follow_back: | ||
337 | enabled: false | ||
338 | |||
339 | # If you want to automatically follow instances of the public index | ||
340 | # If this option is enabled, use the mute feature instead of deleting followings | ||
341 | # /!\ Don't enable this if you don't have a reactive moderation team /!\ | ||
342 | auto_follow_index: | ||
343 | enabled: false | ||
344 | index_url: 'https://instances.joinpeertube.org' | ||
345 | |||
276 | theme: | 346 | theme: |
277 | default: 'default' | 347 | default: 'default' |
diff --git a/config/production.yaml.example b/config/production.yaml.example index 267186e08..96d676a35 100644 --- a/config/production.yaml.example +++ b/config/production.yaml.example | |||
@@ -70,11 +70,11 @@ email: | |||
70 | 70 | ||
71 | # From the project root directory | 71 | # From the project root directory |
72 | storage: | 72 | storage: |
73 | tmp: '/var/www/peertube/storage/tmp/' # Used to download data (imports etc), store uploaded files before processing... | 73 | tmp: '/var/www/peertube/storage/tmp/' # Use to download data (imports etc), store uploaded files before processing... |
74 | avatars: '/var/www/peertube/storage/avatars/' | 74 | avatars: '/var/www/peertube/storage/avatars/' |
75 | videos: '/var/www/peertube/storage/videos/' | 75 | videos: '/var/www/peertube/storage/videos/' |
76 | streaming_playlists: '/var/www/peertube/storage/streaming-playlists/' | 76 | streaming_playlists: '/var/www/peertube/storage/streaming-playlists/' |
77 | redundancy: '/var/www/peertube/storage/videos/' | 77 | redundancy: '/var/www/peertube/storage/redundancy/' |
78 | logs: '/var/www/peertube/storage/logs/' | 78 | logs: '/var/www/peertube/storage/logs/' |
79 | previews: '/var/www/peertube/storage/previews/' | 79 | previews: '/var/www/peertube/storage/previews/' |
80 | thumbnails: '/var/www/peertube/storage/thumbnails/' | 80 | thumbnails: '/var/www/peertube/storage/thumbnails/' |
@@ -86,7 +86,7 @@ storage: | |||
86 | log: | 86 | log: |
87 | level: 'info' # debug/info/warning/error | 87 | level: 'info' # debug/info/warning/error |
88 | rotation: | 88 | rotation: |
89 | enabled : true | 89 | enabled : true # Enabled by default, if disabled make sure that 'storage.logs' is pointing to a folder handled by logrotate |
90 | 90 | ||
91 | search: | 91 | search: |
92 | # Add ability to fetch remote videos/actors by their URI, that may not be federated with your instance | 92 | # Add ability to fetch remote videos/actors by their URI, that may not be federated with your instance |
@@ -157,8 +157,8 @@ views: | |||
157 | max_age: -1 | 157 | max_age: -1 |
158 | 158 | ||
159 | plugins: | 159 | plugins: |
160 | # The website PeerTube will ask for available PeerTube plugins | 160 | # The website PeerTube will ask for available PeerTube plugins and themes |
161 | # This is an unmoderated plugin index, so only install plugins you trust | 161 | # This is an unmoderated plugin index, so only install plugins/themes you trust |
162 | index: | 162 | index: |
163 | enabled: true | 163 | enabled: true |
164 | check_latest_versions_interval: '12 hours' # How often you want to check new plugins/themes versions | 164 | check_latest_versions_interval: '12 hours' # How often you want to check new plugins/themes versions |
@@ -251,9 +251,62 @@ auto_blacklist: | |||
251 | instance: | 251 | instance: |
252 | name: 'PeerTube' | 252 | name: 'PeerTube' |
253 | short_description: 'PeerTube, a federated (ActivityPub) video streaming platform using P2P (BitTorrent) directly in the web browser with WebTorrent and Angular.' | 253 | short_description: 'PeerTube, a federated (ActivityPub) video streaming platform using P2P (BitTorrent) directly in the web browser with WebTorrent and Angular.' |
254 | description: '' # Support markdown | 254 | description: 'Welcome to this PeerTube instance!' # Support markdown |
255 | terms: '' # Support markdown | 255 | terms: 'No terms for now.' # Support markdown |
256 | code_of_conduct: '' # Supports markdown | ||
257 | |||
258 | # Who moderates the instance? What is the policy regarding NSFW videos? Political videos? etc | ||
259 | moderation_information: '' # Supports markdown | ||
260 | |||
261 | # Why did you create this instance? | ||
262 | creation_reason: '' | ||
263 | |||
264 | # Who is behind the instance? A single person? A non profit? | ||
265 | administrator: '' | ||
266 | |||
267 | # How long do you plan to maintain this instance? | ||
268 | maintenance_lifetime: '' | ||
269 | |||
270 | # How will you pay the PeerTube instance server? With you own funds? With users donations? Advertising? | ||
271 | business_model: '' | ||
272 | |||
273 | # If you want to explain on what type of hardware your PeerTube instance runs | ||
274 | # Example: "2 vCore, 2GB RAM..." | ||
275 | hardware_information: '' # Supports Markdown | ||
276 | |||
277 | # What are the main languages of your instance? To interact with your users for example | ||
278 | # Uncomment or add the languages you want | ||
279 | # List of supported languages: https://peertube.cpy.re/api/v1/videos/languages | ||
280 | languages: | ||
281 | # - en | ||
282 | # - es | ||
283 | # - fr | ||
284 | |||
285 | # You can specify the main categories of your instance (dedicated to music, gaming or politics etc) | ||
286 | # Uncomment or add the category ids you want | ||
287 | # List of supported categories: https://peertube.cpy.re/api/v1/videos/categories | ||
288 | categories: | ||
289 | # - 1 # Music | ||
290 | # - 2 # Films | ||
291 | # - 3 # Vehicles | ||
292 | # - 4 # Art | ||
293 | # - 5 # Sports | ||
294 | # - 6 # Travels | ||
295 | # - 7 # Gaming | ||
296 | # - 8 # People | ||
297 | # - 9 # Comedy | ||
298 | # - 10 # Entertainment | ||
299 | # - 11 # News & Politics | ||
300 | # - 12 # How To | ||
301 | # - 13 # Education | ||
302 | # - 14 # Activism | ||
303 | # - 15 # Science & Technology | ||
304 | # - 16 # Animals | ||
305 | # - 17 # Kids | ||
306 | # - 18 # Food | ||
307 | |||
256 | default_client_route: '/videos/trending' | 308 | default_client_route: '/videos/trending' |
309 | |||
257 | # Whether or not the instance is dedicated to NSFW content | 310 | # Whether or not the instance is dedicated to NSFW content |
258 | # Enabling it will allow other administrators to know that you are mainly federating sensitive content | 311 | # Enabling it will allow other administrators to know that you are mainly federating sensitive content |
259 | # Moreover, the NSFW checkbox on video upload will be automatically checked by default | 312 | # Moreover, the NSFW checkbox on video upload will be automatically checked by default |
@@ -261,6 +314,7 @@ instance: | |||
261 | # By default, "do_not_list" or "blur" or "display" NSFW videos | 314 | # By default, "do_not_list" or "blur" or "display" NSFW videos |
262 | # Could be overridden per user with a setting | 315 | # Could be overridden per user with a setting |
263 | default_nsfw_policy: 'do_not_list' | 316 | default_nsfw_policy: 'do_not_list' |
317 | |||
264 | customizations: | 318 | customizations: |
265 | javascript: '' # Directly your JavaScript code (without <script> tags). Will be eval at runtime | 319 | javascript: '' # Directly your JavaScript code (without <script> tags). Will be eval at runtime |
266 | css: '' # Directly your CSS code (without <style> tags). Will be injected at runtime | 320 | css: '' # Directly your CSS code (without <style> tags). Will be injected at runtime |
@@ -278,7 +332,7 @@ services: | |||
278 | username: '@Chocobozzz' # Indicates the Twitter account for the website or platform on which the content was published | 332 | username: '@Chocobozzz' # Indicates the Twitter account for the website or platform on which the content was published |
279 | # If true, a video player will be embedded in the Twitter feed on PeerTube video share | 333 | # If true, a video player will be embedded in the Twitter feed on PeerTube video share |
280 | # If false, we use an image link card that will redirect on your PeerTube instance | 334 | # If false, we use an image link card that will redirect on your PeerTube instance |
281 | # Test on https://cards-dev.twitter.com/validator to see if you are whitelisted | 335 | # Change it to "true", and then test on https://cards-dev.twitter.com/validator to see if you are whitelisted |
282 | whitelisted: false | 336 | whitelisted: false |
283 | 337 | ||
284 | followers: | 338 | followers: |
@@ -288,5 +342,20 @@ followers: | |||
288 | # Whether or not an administrator must manually validate a new follower | 342 | # Whether or not an administrator must manually validate a new follower |
289 | manual_approval: false | 343 | manual_approval: false |
290 | 344 | ||
345 | followings: | ||
346 | instance: | ||
347 | # If you want to automatically follow back new instance followers | ||
348 | # If this option is enabled, use the mute feature instead of deleting followings | ||
349 | # /!\ Don't enable this if you don't have a reactive moderation team /!\ | ||
350 | auto_follow_back: | ||
351 | enabled: false | ||
352 | |||
353 | # If you want to automatically follow instances of the public index | ||
354 | # If this option is enabled, use the mute feature instead of deleting followings | ||
355 | # /!\ Don't enable this if you don't have a reactive moderation team /!\ | ||
356 | auto_follow_index: | ||
357 | enabled: false | ||
358 | index_url: 'https://instances.joinpeertube.org' | ||
359 | |||
291 | theme: | 360 | theme: |
292 | default: 'default' | 361 | default: 'default' |
diff --git a/package.json b/package.json index 3a72fa4aa..ca1f94572 100644 --- a/package.json +++ b/package.json | |||
@@ -128,11 +128,11 @@ | |||
128 | "iso-639-3": "^1.0.1", | 128 | "iso-639-3": "^1.0.1", |
129 | "js-yaml": "^3.5.4", | 129 | "js-yaml": "^3.5.4", |
130 | "jsonld": "~1.1.0", | 130 | "jsonld": "~1.1.0", |
131 | "jsonld-signatures": "https://github.com/Chocobozzz/jsonld-signatures#rsa2017", | ||
132 | "lodash": "^4.17.10", | 131 | "lodash": "^4.17.10", |
133 | "lru-cache": "^5.1.1", | 132 | "lru-cache": "^5.1.1", |
134 | "magnet-uri": "^5.1.4", | 133 | "magnet-uri": "^5.1.4", |
135 | "memoizee": "^0.4.14", | 134 | "memoizee": "^0.4.14", |
135 | "module-alias": "^2.2.1", | ||
136 | "morgan": "^1.5.3", | 136 | "morgan": "^1.5.3", |
137 | "multer": "^1.1.0", | 137 | "multer": "^1.1.0", |
138 | "nodemailer": "^6.0.0", | 138 | "nodemailer": "^6.0.0", |
@@ -225,5 +225,8 @@ | |||
225 | "scripty": { | 225 | "scripty": { |
226 | "silent": true | 226 | "silent": true |
227 | }, | 227 | }, |
228 | "sasslintConfig": "client/.sass-lint.yml" | 228 | "sasslintConfig": "client/.sass-lint.yml", |
229 | "_moduleAliases": { | ||
230 | "@server": "dist/server" | ||
231 | } | ||
229 | } | 232 | } |
diff --git a/scripts/client-report.sh b/scripts/client-report.sh index df7ccda27..a758a211c 100755 --- a/scripts/client-report.sh +++ b/scripts/client-report.sh | |||
@@ -5,5 +5,5 @@ set -eu | |||
5 | gawk -i inplace 'BEGIN { found=0 } { if (found || $0 ~ /^{/) { found=1; print }}' ./client/dist/embed-stats.json | 5 | gawk -i inplace 'BEGIN { found=0 } { if (found || $0 ~ /^{/) { found=1; print }}' ./client/dist/embed-stats.json |
6 | 6 | ||
7 | npm run concurrently -- -k \ | 7 | npm run concurrently -- -k \ |
8 | "cd client && npm run webpack-bundle-analyzer -- -p 8888 ./dist/en_US/stats.json" \ | 8 | "cd client && npm run webpack-bundle-analyzer -- -p 8888 ./dist/en_US/stats-es2015.json" \ |
9 | "cd client && npm run webpack-bundle-analyzer -- -p 8889 ./dist/embed-stats.json" \ No newline at end of file | 9 | "cd client && npm run webpack-bundle-analyzer -- -p 8889 ./dist/embed-stats.json" |
diff --git a/scripts/generate-code-contributors.ts b/scripts/generate-code-contributors.ts index 0d6266056..c745b1cb2 100755 --- a/scripts/generate-code-contributors.ts +++ b/scripts/generate-code-contributors.ts | |||
@@ -13,7 +13,7 @@ async function run () { | |||
13 | { | 13 | { |
14 | const contributors = await fetchGithub('https://api.github.com/repos/chocobozzz/peertube/contributors') | 14 | const contributors = await fetchGithub('https://api.github.com/repos/chocobozzz/peertube/contributors') |
15 | 15 | ||
16 | console.log('# Code\n') | 16 | console.log('# Code contributors\n') |
17 | for (const contributor of contributors) { | 17 | for (const contributor of contributors) { |
18 | const contributorUrl = contributor.url.replace('api.github.com/users', 'github.com') | 18 | const contributorUrl = contributor.url.replace('api.github.com/users', 'github.com') |
19 | console.log(` * [${contributor.login}](${contributorUrl})`) | 19 | console.log(` * [${contributor.login}](${contributorUrl})`) |
@@ -27,7 +27,7 @@ async function run () { | |||
27 | 27 | ||
28 | const translators = await fetchZanata(zanataUsername, zanataToken) | 28 | const translators = await fetchZanata(zanataUsername, zanataToken) |
29 | 29 | ||
30 | console.log('\n\n# Translations\n') | 30 | console.log('\n\n# Translation contributors\n') |
31 | for (const translator of translators) { | 31 | for (const translator of translators) { |
32 | console.log(` * [${translator.username}](https://trad.framasoft.org/zanata/profile/view/${translator.username})`) | 32 | console.log(` * [${translator.username}](https://trad.framasoft.org/zanata/profile/view/${translator.username})`) |
33 | } | 33 | } |
@@ -1,3 +1,5 @@ | |||
1 | require('module-alias/register') | ||
2 | |||
1 | // FIXME: https://github.com/nodejs/node/pull/16853 | 3 | // FIXME: https://github.com/nodejs/node/pull/16853 |
2 | import { PluginManager } from './server/lib/plugins/plugin-manager' | 4 | import { PluginManager } from './server/lib/plugins/plugin-manager' |
3 | 5 | ||
@@ -113,6 +115,7 @@ import { UpdateVideosScheduler } from './server/lib/schedulers/update-videos-sch | |||
113 | import { YoutubeDlUpdateScheduler } from './server/lib/schedulers/youtube-dl-update-scheduler' | 115 | import { YoutubeDlUpdateScheduler } from './server/lib/schedulers/youtube-dl-update-scheduler' |
114 | import { VideosRedundancyScheduler } from './server/lib/schedulers/videos-redundancy-scheduler' | 116 | import { VideosRedundancyScheduler } from './server/lib/schedulers/videos-redundancy-scheduler' |
115 | import { RemoveOldHistoryScheduler } from './server/lib/schedulers/remove-old-history-scheduler' | 117 | import { RemoveOldHistoryScheduler } from './server/lib/schedulers/remove-old-history-scheduler' |
118 | import { AutoFollowIndexInstances } from './server/lib/schedulers/auto-follow-index-instances' | ||
116 | import { isHTTPSignatureDigestValid } from './server/helpers/peertube-crypto' | 119 | import { isHTTPSignatureDigestValid } from './server/helpers/peertube-crypto' |
117 | import { PeerTubeSocket } from './server/lib/peertube-socket' | 120 | import { PeerTubeSocket } from './server/lib/peertube-socket' |
118 | import { updateStreamingPlaylistsInfohashesIfNeeded } from './server/lib/hls' | 121 | import { updateStreamingPlaylistsInfohashesIfNeeded } from './server/lib/hls' |
@@ -258,6 +261,7 @@ async function startApplication () { | |||
258 | RemoveOldHistoryScheduler.Instance.enable() | 261 | RemoveOldHistoryScheduler.Instance.enable() |
259 | RemoveOldViewsScheduler.Instance.enable() | 262 | RemoveOldViewsScheduler.Instance.enable() |
260 | PluginsCheckScheduler.Instance.enable() | 263 | PluginsCheckScheduler.Instance.enable() |
264 | AutoFollowIndexInstances.Instance.enable() | ||
261 | 265 | ||
262 | // Redis initialization | 266 | // Redis initialization |
263 | Redis.Instance.init() | 267 | Redis.Instance.init() |
diff --git a/server/controllers/activitypub/client.ts b/server/controllers/activitypub/client.ts index 11504b354..453ced8bf 100644 --- a/server/controllers/activitypub/client.ts +++ b/server/controllers/activitypub/client.ts | |||
@@ -16,7 +16,6 @@ import { | |||
16 | } from '../../middlewares' | 16 | } from '../../middlewares' |
17 | import { getAccountVideoRateValidator, videoCommentGetValidator } from '../../middlewares/validators' | 17 | import { getAccountVideoRateValidator, videoCommentGetValidator } from '../../middlewares/validators' |
18 | import { AccountModel } from '../../models/account/account' | 18 | import { AccountModel } from '../../models/account/account' |
19 | import { ActorModel } from '../../models/activitypub/actor' | ||
20 | import { ActorFollowModel } from '../../models/activitypub/actor-follow' | 19 | import { ActorFollowModel } from '../../models/activitypub/actor-follow' |
21 | import { VideoModel } from '../../models/video/video' | 20 | import { VideoModel } from '../../models/video/video' |
22 | import { VideoCommentModel } from '../../models/video/video-comment' | 21 | import { VideoCommentModel } from '../../models/video/video-comment' |
@@ -38,6 +37,7 @@ import { buildDislikeActivity } from '../../lib/activitypub/send/send-dislike' | |||
38 | import { videoPlaylistElementAPGetValidator, videoPlaylistsGetValidator } from '../../middlewares/validators/videos/video-playlists' | 37 | import { videoPlaylistElementAPGetValidator, videoPlaylistsGetValidator } from '../../middlewares/validators/videos/video-playlists' |
39 | import { VideoPlaylistModel } from '../../models/video/video-playlist' | 38 | import { VideoPlaylistModel } from '../../models/video/video-playlist' |
40 | import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model' | 39 | import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model' |
40 | import { MAccountId, MActorId, MVideo, MVideoAPWithoutCaption } from '@server/typings/models' | ||
41 | 41 | ||
42 | const activityPubClientRouter = express.Router() | 42 | const activityPubClientRouter = express.Router() |
43 | 43 | ||
@@ -148,7 +148,7 @@ activityPubClientRouter.get('/redundancy/streaming-playlists/:streamingPlaylistT | |||
148 | 148 | ||
149 | activityPubClientRouter.get('/video-playlists/:playlistId', | 149 | activityPubClientRouter.get('/video-playlists/:playlistId', |
150 | executeIfActivityPub, | 150 | executeIfActivityPub, |
151 | asyncMiddleware(videoPlaylistsGetValidator), | 151 | asyncMiddleware(videoPlaylistsGetValidator('all')), |
152 | asyncMiddleware(videoPlaylistController) | 152 | asyncMiddleware(videoPlaylistController) |
153 | ) | 153 | ) |
154 | activityPubClientRouter.get('/video-playlists/:playlistId/:videoId', | 154 | activityPubClientRouter.get('/video-playlists/:playlistId/:videoId', |
@@ -208,18 +208,19 @@ function getAccountVideoRate (rateType: VideoRateType) { | |||
208 | 208 | ||
209 | async function videoController (req: express.Request, res: express.Response) { | 209 | async function videoController (req: express.Request, res: express.Response) { |
210 | // We need more attributes | 210 | // We need more attributes |
211 | const video = await VideoModel.loadForGetAPI({ id: res.locals.video.id }) | 211 | const video = await VideoModel.loadForGetAPI({ id: res.locals.onlyVideoWithRights.id }) as MVideoAPWithoutCaption |
212 | 212 | ||
213 | if (video.url.startsWith(WEBSERVER.URL) === false) return res.redirect(video.url) | 213 | if (video.url.startsWith(WEBSERVER.URL) === false) return res.redirect(video.url) |
214 | 214 | ||
215 | // We need captions to render AP object | 215 | // We need captions to render AP object |
216 | video.VideoCaptions = await VideoCaptionModel.listVideoCaptions(video.id) | 216 | const captions = await VideoCaptionModel.listVideoCaptions(video.id) |
217 | const videoWithCaptions = Object.assign(video, { VideoCaptions: captions }) | ||
217 | 218 | ||
218 | const audience = getAudience(video.VideoChannel.Account.Actor, video.privacy === VideoPrivacy.PUBLIC) | 219 | const audience = getAudience(videoWithCaptions.VideoChannel.Account.Actor, videoWithCaptions.privacy === VideoPrivacy.PUBLIC) |
219 | const videoObject = audiencify(video.toActivityPubObject(), audience) | 220 | const videoObject = audiencify(videoWithCaptions.toActivityPubObject(), audience) |
220 | 221 | ||
221 | if (req.path.endsWith('/activity')) { | 222 | if (req.path.endsWith('/activity')) { |
222 | const data = buildCreateActivity(video.url, video.VideoChannel.Account.Actor, videoObject, audience) | 223 | const data = buildCreateActivity(videoWithCaptions.url, video.VideoChannel.Account.Actor, videoObject, audience) |
223 | return activityPubResponse(activityPubContextify(data), res) | 224 | return activityPubResponse(activityPubContextify(data), res) |
224 | } | 225 | } |
225 | 226 | ||
@@ -231,13 +232,13 @@ async function videoAnnounceController (req: express.Request, res: express.Respo | |||
231 | 232 | ||
232 | if (share.url.startsWith(WEBSERVER.URL) === false) return res.redirect(share.url) | 233 | if (share.url.startsWith(WEBSERVER.URL) === false) return res.redirect(share.url) |
233 | 234 | ||
234 | const { activity } = await buildAnnounceWithVideoAudience(share.Actor, share, res.locals.video, undefined) | 235 | const { activity } = await buildAnnounceWithVideoAudience(share.Actor, share, res.locals.videoAll, undefined) |
235 | 236 | ||
236 | return activityPubResponse(activityPubContextify(activity), res) | 237 | return activityPubResponse(activityPubContextify(activity), res) |
237 | } | 238 | } |
238 | 239 | ||
239 | async function videoAnnouncesController (req: express.Request, res: express.Response) { | 240 | async function videoAnnouncesController (req: express.Request, res: express.Response) { |
240 | const video = res.locals.video | 241 | const video = res.locals.onlyVideo |
241 | 242 | ||
242 | const handler = async (start: number, count: number) => { | 243 | const handler = async (start: number, count: number) => { |
243 | const result = await VideoShareModel.listAndCountByVideoId(video.id, start, count) | 244 | const result = await VideoShareModel.listAndCountByVideoId(video.id, start, count) |
@@ -252,21 +253,21 @@ async function videoAnnouncesController (req: express.Request, res: express.Resp | |||
252 | } | 253 | } |
253 | 254 | ||
254 | async function videoLikesController (req: express.Request, res: express.Response) { | 255 | async function videoLikesController (req: express.Request, res: express.Response) { |
255 | const video = res.locals.video | 256 | const video = res.locals.onlyVideo |
256 | const json = await videoRates(req, 'like', video, getVideoLikesActivityPubUrl(video)) | 257 | const json = await videoRates(req, 'like', video, getVideoLikesActivityPubUrl(video)) |
257 | 258 | ||
258 | return activityPubResponse(activityPubContextify(json), res) | 259 | return activityPubResponse(activityPubContextify(json), res) |
259 | } | 260 | } |
260 | 261 | ||
261 | async function videoDislikesController (req: express.Request, res: express.Response) { | 262 | async function videoDislikesController (req: express.Request, res: express.Response) { |
262 | const video = res.locals.video | 263 | const video = res.locals.onlyVideo |
263 | const json = await videoRates(req, 'dislike', video, getVideoDislikesActivityPubUrl(video)) | 264 | const json = await videoRates(req, 'dislike', video, getVideoDislikesActivityPubUrl(video)) |
264 | 265 | ||
265 | return activityPubResponse(activityPubContextify(json), res) | 266 | return activityPubResponse(activityPubContextify(json), res) |
266 | } | 267 | } |
267 | 268 | ||
268 | async function videoCommentsController (req: express.Request, res: express.Response) { | 269 | async function videoCommentsController (req: express.Request, res: express.Response) { |
269 | const video = res.locals.video | 270 | const video = res.locals.onlyVideo |
270 | 271 | ||
271 | const handler = async (start: number, count: number) => { | 272 | const handler = async (start: number, count: number) => { |
272 | const result = await VideoCommentModel.listAndCountByVideoId(video.id, start, count) | 273 | const result = await VideoCommentModel.listAndCountByVideoId(video.id, start, count) |
@@ -301,7 +302,7 @@ async function videoChannelFollowingController (req: express.Request, res: expre | |||
301 | } | 302 | } |
302 | 303 | ||
303 | async function videoCommentController (req: express.Request, res: express.Response) { | 304 | async function videoCommentController (req: express.Request, res: express.Response) { |
304 | const videoComment = res.locals.videoComment | 305 | const videoComment = res.locals.videoCommentFull |
305 | 306 | ||
306 | if (videoComment.url.startsWith(WEBSERVER.URL) === false) return res.redirect(videoComment.url) | 307 | if (videoComment.url.startsWith(WEBSERVER.URL) === false) return res.redirect(videoComment.url) |
307 | 308 | ||
@@ -337,7 +338,7 @@ async function videoRedundancyController (req: express.Request, res: express.Res | |||
337 | } | 338 | } |
338 | 339 | ||
339 | async function videoPlaylistController (req: express.Request, res: express.Response) { | 340 | async function videoPlaylistController (req: express.Request, res: express.Response) { |
340 | const playlist = res.locals.videoPlaylist | 341 | const playlist = res.locals.videoPlaylistFull |
341 | 342 | ||
342 | // We need more attributes | 343 | // We need more attributes |
343 | playlist.OwnerAccount = await AccountModel.load(playlist.ownerAccountId) | 344 | playlist.OwnerAccount = await AccountModel.load(playlist.ownerAccountId) |
@@ -350,7 +351,7 @@ async function videoPlaylistController (req: express.Request, res: express.Respo | |||
350 | } | 351 | } |
351 | 352 | ||
352 | async function videoPlaylistElementController (req: express.Request, res: express.Response) { | 353 | async function videoPlaylistElementController (req: express.Request, res: express.Response) { |
353 | const videoPlaylistElement = res.locals.videoPlaylistElement | 354 | const videoPlaylistElement = res.locals.videoPlaylistElementAP |
354 | 355 | ||
355 | const json = videoPlaylistElement.toActivityPubObject() | 356 | const json = videoPlaylistElement.toActivityPubObject() |
356 | return activityPubResponse(activityPubContextify(json), res) | 357 | return activityPubResponse(activityPubContextify(json), res) |
@@ -358,7 +359,7 @@ async function videoPlaylistElementController (req: express.Request, res: expres | |||
358 | 359 | ||
359 | // --------------------------------------------------------------------------- | 360 | // --------------------------------------------------------------------------- |
360 | 361 | ||
361 | async function actorFollowing (req: express.Request, actor: ActorModel) { | 362 | async function actorFollowing (req: express.Request, actor: MActorId) { |
362 | const handler = (start: number, count: number) => { | 363 | const handler = (start: number, count: number) => { |
363 | return ActorFollowModel.listAcceptedFollowingUrlsForApi([ actor.id ], undefined, start, count) | 364 | return ActorFollowModel.listAcceptedFollowingUrlsForApi([ actor.id ], undefined, start, count) |
364 | } | 365 | } |
@@ -366,7 +367,7 @@ async function actorFollowing (req: express.Request, actor: ActorModel) { | |||
366 | return activityPubCollectionPagination(WEBSERVER.URL + req.path, handler, req.query.page) | 367 | return activityPubCollectionPagination(WEBSERVER.URL + req.path, handler, req.query.page) |
367 | } | 368 | } |
368 | 369 | ||
369 | async function actorFollowers (req: express.Request, actor: ActorModel) { | 370 | async function actorFollowers (req: express.Request, actor: MActorId) { |
370 | const handler = (start: number, count: number) => { | 371 | const handler = (start: number, count: number) => { |
371 | return ActorFollowModel.listAcceptedFollowerUrlsForAP([ actor.id ], undefined, start, count) | 372 | return ActorFollowModel.listAcceptedFollowerUrlsForAP([ actor.id ], undefined, start, count) |
372 | } | 373 | } |
@@ -374,7 +375,7 @@ async function actorFollowers (req: express.Request, actor: ActorModel) { | |||
374 | return activityPubCollectionPagination(WEBSERVER.URL + req.path, handler, req.query.page) | 375 | return activityPubCollectionPagination(WEBSERVER.URL + req.path, handler, req.query.page) |
375 | } | 376 | } |
376 | 377 | ||
377 | async function actorPlaylists (req: express.Request, account: AccountModel) { | 378 | async function actorPlaylists (req: express.Request, account: MAccountId) { |
378 | const handler = (start: number, count: number) => { | 379 | const handler = (start: number, count: number) => { |
379 | return VideoPlaylistModel.listPublicUrlsOfForAP(account.id, start, count) | 380 | return VideoPlaylistModel.listPublicUrlsOfForAP(account.id, start, count) |
380 | } | 381 | } |
@@ -382,7 +383,7 @@ async function actorPlaylists (req: express.Request, account: AccountModel) { | |||
382 | return activityPubCollectionPagination(WEBSERVER.URL + req.path, handler, req.query.page) | 383 | return activityPubCollectionPagination(WEBSERVER.URL + req.path, handler, req.query.page) |
383 | } | 384 | } |
384 | 385 | ||
385 | function videoRates (req: express.Request, rateType: VideoRateType, video: VideoModel, url: string) { | 386 | function videoRates (req: express.Request, rateType: VideoRateType, video: MVideo, url: string) { |
386 | const handler = async (start: number, count: number) => { | 387 | const handler = async (start: number, count: number) => { |
387 | const result = await AccountVideoRateModel.listAndCountAccountUrlsByVideoId(rateType, video.id, start, count) | 388 | const result = await AccountVideoRateModel.listAndCountAccountUrlsByVideoId(rateType, video.id, start, count) |
388 | return { | 389 | return { |
diff --git a/server/controllers/activitypub/inbox.ts b/server/controllers/activitypub/inbox.ts index 2d3eef222..d9df253aa 100644 --- a/server/controllers/activitypub/inbox.ts +++ b/server/controllers/activitypub/inbox.ts | |||
@@ -7,7 +7,7 @@ import { asyncMiddleware, checkSignature, localAccountValidator, localVideoChann | |||
7 | import { activityPubValidator } from '../../middlewares/validators/activitypub/activity' | 7 | import { activityPubValidator } from '../../middlewares/validators/activitypub/activity' |
8 | import { queue } from 'async' | 8 | import { queue } from 'async' |
9 | import { ActorModel } from '../../models/activitypub/actor' | 9 | import { ActorModel } from '../../models/activitypub/actor' |
10 | import { SignatureActorModel } from '../../typings/models' | 10 | import { MActorDefault, MActorSignature } from '../../typings/models' |
11 | 11 | ||
12 | const inboxRouter = express.Router() | 12 | const inboxRouter = express.Router() |
13 | 13 | ||
@@ -41,7 +41,8 @@ export { | |||
41 | 41 | ||
42 | // --------------------------------------------------------------------------- | 42 | // --------------------------------------------------------------------------- |
43 | 43 | ||
44 | const inboxQueue = queue<{ activities: Activity[], signatureActor?: SignatureActorModel, inboxActor?: ActorModel }, Error>((task, cb) => { | 44 | type QueueParam = { activities: Activity[], signatureActor?: MActorSignature, inboxActor?: MActorDefault } |
45 | const inboxQueue = queue<QueueParam, Error>((task, cb) => { | ||
45 | const options = { signatureActor: task.signatureActor, inboxActor: task.inboxActor } | 46 | const options = { signatureActor: task.signatureActor, inboxActor: task.inboxActor } |
46 | 47 | ||
47 | processActivities(task.activities, options) | 48 | processActivities(task.activities, options) |
diff --git a/server/controllers/activitypub/outbox.ts b/server/controllers/activitypub/outbox.ts index 38b6ec976..f3dd2ad7d 100644 --- a/server/controllers/activitypub/outbox.ts +++ b/server/controllers/activitypub/outbox.ts | |||
@@ -6,11 +6,9 @@ import { logger } from '../../helpers/logger' | |||
6 | import { buildAnnounceActivity, buildCreateActivity } from '../../lib/activitypub/send' | 6 | import { buildAnnounceActivity, buildCreateActivity } from '../../lib/activitypub/send' |
7 | import { buildAudience } from '../../lib/activitypub/audience' | 7 | import { buildAudience } from '../../lib/activitypub/audience' |
8 | import { asyncMiddleware, localAccountValidator, localVideoChannelValidator } from '../../middlewares' | 8 | import { asyncMiddleware, localAccountValidator, localVideoChannelValidator } from '../../middlewares' |
9 | import { AccountModel } from '../../models/account/account' | ||
10 | import { ActorModel } from '../../models/activitypub/actor' | ||
11 | import { VideoModel } from '../../models/video/video' | 9 | import { VideoModel } from '../../models/video/video' |
12 | import { activityPubResponse } from './utils' | 10 | import { activityPubResponse } from './utils' |
13 | import { VideoChannelModel } from '../../models/video/video-channel' | 11 | import { MActorLight } from '@server/typings/models' |
14 | 12 | ||
15 | const outboxRouter = express.Router() | 13 | const outboxRouter = express.Router() |
16 | 14 | ||
@@ -45,14 +43,10 @@ async function outboxController (req: express.Request, res: express.Response) { | |||
45 | return activityPubResponse(activityPubContextify(json), res) | 43 | return activityPubResponse(activityPubContextify(json), res) |
46 | } | 44 | } |
47 | 45 | ||
48 | async function buildActivities (actor: ActorModel, start: number, count: number) { | 46 | async function buildActivities (actor: MActorLight, start: number, count: number) { |
49 | const data = await VideoModel.listAllAndSharedByActorForOutbox(actor.id, start, count) | 47 | const data = await VideoModel.listAllAndSharedByActorForOutbox(actor.id, start, count) |
50 | const activities: Activity[] = [] | 48 | const activities: Activity[] = [] |
51 | 49 | ||
52 | // Avoid too many SQL requests | ||
53 | const actors = data.data.map(v => v.VideoChannel.Account.Actor) | ||
54 | actors.push(actor) | ||
55 | |||
56 | for (const video of data.data) { | 50 | for (const video of data.data) { |
57 | const byActor = video.VideoChannel.Account.Actor | 51 | const byActor = video.VideoChannel.Account.Actor |
58 | const createActivityAudience = buildAudience([ byActor.followersUrl ], video.privacy === VideoPrivacy.PUBLIC) | 52 | const createActivityAudience = buildAudience([ byActor.followersUrl ], video.privacy === VideoPrivacy.PUBLIC) |
diff --git a/server/controllers/api/config.ts b/server/controllers/api/config.ts index 21fa85a08..39a124fc5 100644 --- a/server/controllers/api/config.ts +++ b/server/controllers/api/config.ts | |||
@@ -158,7 +158,19 @@ function getAbout (req: express.Request, res: express.Response) { | |||
158 | name: CONFIG.INSTANCE.NAME, | 158 | name: CONFIG.INSTANCE.NAME, |
159 | shortDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION, | 159 | shortDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION, |
160 | description: CONFIG.INSTANCE.DESCRIPTION, | 160 | description: CONFIG.INSTANCE.DESCRIPTION, |
161 | terms: CONFIG.INSTANCE.TERMS | 161 | terms: CONFIG.INSTANCE.TERMS, |
162 | codeOfConduct: CONFIG.INSTANCE.CODE_OF_CONDUCT, | ||
163 | |||
164 | hardwareInformation: CONFIG.INSTANCE.HARDWARE_INFORMATION, | ||
165 | |||
166 | creationReason: CONFIG.INSTANCE.CREATION_REASON, | ||
167 | moderationInformation: CONFIG.INSTANCE.MODERATION_INFORMATION, | ||
168 | administrator: CONFIG.INSTANCE.ADMINISTRATOR, | ||
169 | maintenanceLifetime: CONFIG.INSTANCE.MAINTENANCE_LIFETIME, | ||
170 | businessModel: CONFIG.INSTANCE.BUSINESS_MODEL, | ||
171 | |||
172 | languages: CONFIG.INSTANCE.LANGUAGES, | ||
173 | categories: CONFIG.INSTANCE.CATEGORIES | ||
162 | } | 174 | } |
163 | } | 175 | } |
164 | 176 | ||
@@ -221,6 +233,18 @@ function customConfig (): CustomConfig { | |||
221 | shortDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION, | 233 | shortDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION, |
222 | description: CONFIG.INSTANCE.DESCRIPTION, | 234 | description: CONFIG.INSTANCE.DESCRIPTION, |
223 | terms: CONFIG.INSTANCE.TERMS, | 235 | terms: CONFIG.INSTANCE.TERMS, |
236 | codeOfConduct: CONFIG.INSTANCE.CODE_OF_CONDUCT, | ||
237 | |||
238 | creationReason: CONFIG.INSTANCE.CREATION_REASON, | ||
239 | moderationInformation: CONFIG.INSTANCE.MODERATION_INFORMATION, | ||
240 | administrator: CONFIG.INSTANCE.ADMINISTRATOR, | ||
241 | maintenanceLifetime: CONFIG.INSTANCE.MAINTENANCE_LIFETIME, | ||
242 | businessModel: CONFIG.INSTANCE.BUSINESS_MODEL, | ||
243 | hardwareInformation: CONFIG.INSTANCE.HARDWARE_INFORMATION, | ||
244 | |||
245 | languages: CONFIG.INSTANCE.LANGUAGES, | ||
246 | categories: CONFIG.INSTANCE.CATEGORIES, | ||
247 | |||
224 | isNSFW: CONFIG.INSTANCE.IS_NSFW, | 248 | isNSFW: CONFIG.INSTANCE.IS_NSFW, |
225 | defaultClientRoute: CONFIG.INSTANCE.DEFAULT_CLIENT_ROUTE, | 249 | defaultClientRoute: CONFIG.INSTANCE.DEFAULT_CLIENT_ROUTE, |
226 | defaultNSFWPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY, | 250 | defaultNSFWPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY, |
@@ -300,6 +324,18 @@ function customConfig (): CustomConfig { | |||
300 | enabled: CONFIG.FOLLOWERS.INSTANCE.ENABLED, | 324 | enabled: CONFIG.FOLLOWERS.INSTANCE.ENABLED, |
301 | manualApproval: CONFIG.FOLLOWERS.INSTANCE.MANUAL_APPROVAL | 325 | manualApproval: CONFIG.FOLLOWERS.INSTANCE.MANUAL_APPROVAL |
302 | } | 326 | } |
327 | }, | ||
328 | followings: { | ||
329 | instance: { | ||
330 | autoFollowBack: { | ||
331 | enabled: CONFIG.FOLLOWINGS.INSTANCE.AUTO_FOLLOW_BACK.ENABLED | ||
332 | }, | ||
333 | |||
334 | autoFollowIndex: { | ||
335 | enabled: CONFIG.FOLLOWINGS.INSTANCE.AUTO_FOLLOW_INDEX.ENABLED, | ||
336 | indexUrl: CONFIG.FOLLOWINGS.INSTANCE.AUTO_FOLLOW_INDEX.INDEX_URL | ||
337 | } | ||
338 | } | ||
303 | } | 339 | } |
304 | } | 340 | } |
305 | } | 341 | } |
diff --git a/server/controllers/api/search.ts b/server/controllers/api/search.ts index 9a1e30b83..349650aca 100644 --- a/server/controllers/api/search.ts +++ b/server/controllers/api/search.ts | |||
@@ -19,6 +19,7 @@ import { getOrCreateActorAndServerAndModel, getOrCreateVideoAndAccountAndChannel | |||
19 | import { logger } from '../../helpers/logger' | 19 | import { logger } from '../../helpers/logger' |
20 | import { VideoChannelModel } from '../../models/video/video-channel' | 20 | import { VideoChannelModel } from '../../models/video/video-channel' |
21 | import { loadActorUrlOrGetFromWebfinger } from '../../helpers/webfinger' | 21 | import { loadActorUrlOrGetFromWebfinger } from '../../helpers/webfinger' |
22 | import { MChannelAccountDefault, MVideoAccountLightBlacklistAllFiles } from '../../typings/models' | ||
22 | 23 | ||
23 | const searchRouter = express.Router() | 24 | const searchRouter = express.Router() |
24 | 25 | ||
@@ -84,7 +85,7 @@ async function searchVideoChannelsDB (query: VideoChannelsSearchQuery, res: expr | |||
84 | } | 85 | } |
85 | 86 | ||
86 | async function searchVideoChannelURI (search: string, isWebfingerSearch: boolean, res: express.Response) { | 87 | async function searchVideoChannelURI (search: string, isWebfingerSearch: boolean, res: express.Response) { |
87 | let videoChannel: VideoChannelModel | 88 | let videoChannel: MChannelAccountDefault |
88 | let uri = search | 89 | let uri = search |
89 | 90 | ||
90 | if (isWebfingerSearch) { | 91 | if (isWebfingerSearch) { |
@@ -137,7 +138,7 @@ async function searchVideosDB (query: VideosSearchQuery, res: express.Response) | |||
137 | } | 138 | } |
138 | 139 | ||
139 | async function searchVideoURI (url: string, res: express.Response) { | 140 | async function searchVideoURI (url: string, res: express.Response) { |
140 | let video: VideoModel | 141 | let video: MVideoAccountLightBlacklistAllFiles |
141 | 142 | ||
142 | // Check if we can fetch a remote video with the URL | 143 | // Check if we can fetch a remote video with the URL |
143 | if (isUserAbleToSearchRemoteURI(res)) { | 144 | if (isUserAbleToSearchRemoteURI(res)) { |
diff --git a/server/controllers/api/server/follows.ts b/server/controllers/api/server/follows.ts index d38ce91de..37647622b 100644 --- a/server/controllers/api/server/follows.ts +++ b/server/controllers/api/server/follows.ts | |||
@@ -25,6 +25,7 @@ import { ActorFollowModel } from '../../../models/activitypub/actor-follow' | |||
25 | import { JobQueue } from '../../../lib/job-queue' | 25 | import { JobQueue } from '../../../lib/job-queue' |
26 | import { removeRedundancyOf } from '../../../lib/redundancy' | 26 | import { removeRedundancyOf } from '../../../lib/redundancy' |
27 | import { sequelizeTypescript } from '../../../initializers/database' | 27 | import { sequelizeTypescript } from '../../../initializers/database' |
28 | import { autoFollowBackIfNeeded } from '../../../lib/activitypub/follow' | ||
28 | 29 | ||
29 | const serverFollowsRouter = express.Router() | 30 | const serverFollowsRouter = express.Router() |
30 | serverFollowsRouter.get('/following', | 31 | serverFollowsRouter.get('/following', |
@@ -172,5 +173,7 @@ async function acceptFollower (req: express.Request, res: express.Response) { | |||
172 | follow.state = 'accepted' | 173 | follow.state = 'accepted' |
173 | await follow.save() | 174 | await follow.save() |
174 | 175 | ||
176 | await autoFollowBackIfNeeded(follow) | ||
177 | |||
175 | return res.status(204).end() | 178 | return res.status(204).end() |
176 | } | 179 | } |
diff --git a/server/controllers/api/users/index.ts b/server/controllers/api/users/index.ts index ae40e86f8..27351c1a9 100644 --- a/server/controllers/api/users/index.ts +++ b/server/controllers/api/users/index.ts | |||
@@ -48,6 +48,7 @@ import { CONFIG } from '../../../initializers/config' | |||
48 | import { sequelizeTypescript } from '../../../initializers/database' | 48 | import { sequelizeTypescript } from '../../../initializers/database' |
49 | import { UserAdminFlag } from '../../../../shared/models/users/user-flag.model' | 49 | import { UserAdminFlag } from '../../../../shared/models/users/user-flag.model' |
50 | import { UserRegister } from '../../../../shared/models/users/user-register.model' | 50 | import { UserRegister } from '../../../../shared/models/users/user-register.model' |
51 | import { MUser, MUserAccountDefault } from '@server/typings/models' | ||
51 | 52 | ||
52 | const auditLogger = auditLoggerFactory('users') | 53 | const auditLogger = auditLoggerFactory('users') |
53 | 54 | ||
@@ -195,7 +196,7 @@ async function createUser (req: express.Request, res: express.Response) { | |||
195 | videoQuota: body.videoQuota, | 196 | videoQuota: body.videoQuota, |
196 | videoQuotaDaily: body.videoQuotaDaily, | 197 | videoQuotaDaily: body.videoQuotaDaily, |
197 | adminFlags: body.adminFlags || UserAdminFlag.NONE | 198 | adminFlags: body.adminFlags || UserAdminFlag.NONE |
198 | }) | 199 | }) as MUser |
199 | 200 | ||
200 | const { user, account } = await createUserAccountAndChannelAndPlaylist({ userToCreate: userToCreate }) | 201 | const { user, account } = await createUserAccountAndChannelAndPlaylist({ userToCreate: userToCreate }) |
201 | 202 | ||
@@ -359,7 +360,7 @@ function success (req: express.Request, res: express.Response) { | |||
359 | res.end() | 360 | res.end() |
360 | } | 361 | } |
361 | 362 | ||
362 | async function changeUserBlock (res: express.Response, user: UserModel, block: boolean, reason?: string) { | 363 | async function changeUserBlock (res: express.Response, user: MUserAccountDefault, block: boolean, reason?: string) { |
363 | const oldUserAuditView = new UserAuditView(user.toFormattedJSON()) | 364 | const oldUserAuditView = new UserAuditView(user.toFormattedJSON()) |
364 | 365 | ||
365 | user.blocked = block | 366 | user.blocked = block |
diff --git a/server/controllers/api/users/me.ts b/server/controllers/api/users/me.ts index e7ed3de64..bf872ca52 100644 --- a/server/controllers/api/users/me.ts +++ b/server/controllers/api/users/me.ts | |||
@@ -23,15 +23,12 @@ import { createReqFiles } from '../../../helpers/express-utils' | |||
23 | import { UserVideoQuota } from '../../../../shared/models/users/user-video-quota.model' | 23 | import { UserVideoQuota } from '../../../../shared/models/users/user-video-quota.model' |
24 | import { updateAvatarValidator } from '../../../middlewares/validators/avatar' | 24 | import { updateAvatarValidator } from '../../../middlewares/validators/avatar' |
25 | import { updateActorAvatarFile } from '../../../lib/avatar' | 25 | import { updateActorAvatarFile } from '../../../lib/avatar' |
26 | import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '../../../helpers/audit-logger' | ||
27 | import { VideoImportModel } from '../../../models/video/video-import' | 26 | import { VideoImportModel } from '../../../models/video/video-import' |
28 | import { AccountModel } from '../../../models/account/account' | 27 | import { AccountModel } from '../../../models/account/account' |
29 | import { CONFIG } from '../../../initializers/config' | 28 | import { CONFIG } from '../../../initializers/config' |
30 | import { sequelizeTypescript } from '../../../initializers/database' | 29 | import { sequelizeTypescript } from '../../../initializers/database' |
31 | import { sendVerifyUserEmail } from '../../../lib/user' | 30 | import { sendVerifyUserEmail } from '../../../lib/user' |
32 | 31 | ||
33 | const auditLogger = auditLoggerFactory('users-me') | ||
34 | |||
35 | const reqAvatarFile = createReqFiles([ 'avatarfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.TMP_DIR }) | 32 | const reqAvatarFile = createReqFiles([ 'avatarfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.TMP_DIR }) |
36 | 33 | ||
37 | const meRouter = express.Router() | 34 | const meRouter = express.Router() |
@@ -130,7 +127,7 @@ async function getUserInformation (req: express.Request, res: express.Response) | |||
130 | // We did not load channels in res.locals.user | 127 | // We did not load channels in res.locals.user |
131 | const user = await UserModel.loadByUsernameAndPopulateChannels(res.locals.oauth.token.user.username) | 128 | const user = await UserModel.loadByUsernameAndPopulateChannels(res.locals.oauth.token.user.username) |
132 | 129 | ||
133 | return res.json(user.toFormattedJSON({})) | 130 | return res.json(user.toFormattedJSON()) |
134 | } | 131 | } |
135 | 132 | ||
136 | async function getUserVideoQuotaUsed (req: express.Request, res: express.Response) { | 133 | async function getUserVideoQuotaUsed (req: express.Request, res: express.Response) { |
@@ -147,7 +144,7 @@ async function getUserVideoQuotaUsed (req: express.Request, res: express.Respons | |||
147 | } | 144 | } |
148 | 145 | ||
149 | async function getUserVideoRating (req: express.Request, res: express.Response) { | 146 | async function getUserVideoRating (req: express.Request, res: express.Response) { |
150 | const videoId = res.locals.video.id | 147 | const videoId = res.locals.videoId.id |
151 | const accountId = +res.locals.oauth.token.User.Account.id | 148 | const accountId = +res.locals.oauth.token.User.Account.id |
152 | 149 | ||
153 | const ratingObj = await AccountVideoRateModel.load(accountId, videoId, null) | 150 | const ratingObj = await AccountVideoRateModel.load(accountId, videoId, null) |
@@ -165,8 +162,6 @@ async function deleteMe (req: express.Request, res: express.Response) { | |||
165 | 162 | ||
166 | await user.destroy() | 163 | await user.destroy() |
167 | 164 | ||
168 | auditLogger.delete(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON({}))) | ||
169 | |||
170 | return res.sendStatus(204) | 165 | return res.sendStatus(204) |
171 | } | 166 | } |
172 | 167 | ||
@@ -175,7 +170,6 @@ async function updateMe (req: express.Request, res: express.Response) { | |||
175 | let sendVerificationEmail = false | 170 | let sendVerificationEmail = false |
176 | 171 | ||
177 | const user = res.locals.oauth.token.user | 172 | const user = res.locals.oauth.token.user |
178 | const oldUserAuditView = new UserAuditView(user.toFormattedJSON({})) | ||
179 | 173 | ||
180 | if (body.password !== undefined) user.password = body.password | 174 | if (body.password !== undefined) user.password = body.password |
181 | if (body.nsfwPolicy !== undefined) user.nsfwPolicy = body.nsfwPolicy | 175 | if (body.nsfwPolicy !== undefined) user.nsfwPolicy = body.nsfwPolicy |
@@ -184,6 +178,8 @@ async function updateMe (req: express.Request, res: express.Response) { | |||
184 | if (body.videosHistoryEnabled !== undefined) user.videosHistoryEnabled = body.videosHistoryEnabled | 178 | if (body.videosHistoryEnabled !== undefined) user.videosHistoryEnabled = body.videosHistoryEnabled |
185 | if (body.videoLanguages !== undefined) user.videoLanguages = body.videoLanguages | 179 | if (body.videoLanguages !== undefined) user.videoLanguages = body.videoLanguages |
186 | if (body.theme !== undefined) user.theme = body.theme | 180 | if (body.theme !== undefined) user.theme = body.theme |
181 | if (body.noInstanceConfigWarningModal !== undefined) user.noInstanceConfigWarningModal = body.noInstanceConfigWarningModal | ||
182 | if (body.noWelcomeModal !== undefined) user.noWelcomeModal = body.noWelcomeModal | ||
187 | 183 | ||
188 | if (body.email !== undefined) { | 184 | if (body.email !== undefined) { |
189 | if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION) { | 185 | if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION) { |
@@ -195,17 +191,17 @@ async function updateMe (req: express.Request, res: express.Response) { | |||
195 | } | 191 | } |
196 | 192 | ||
197 | await sequelizeTypescript.transaction(async t => { | 193 | await sequelizeTypescript.transaction(async t => { |
198 | const userAccount = await AccountModel.load(user.Account.id) | ||
199 | |||
200 | await user.save({ transaction: t }) | 194 | await user.save({ transaction: t }) |
201 | 195 | ||
202 | if (body.displayName !== undefined) userAccount.name = body.displayName | 196 | if (body.displayName !== undefined || body.description !== undefined) { |
203 | if (body.description !== undefined) userAccount.description = body.description | 197 | const userAccount = await AccountModel.load(user.Account.id, t) |
204 | await userAccount.save({ transaction: t }) | ||
205 | 198 | ||
206 | await sendUpdateActor(userAccount, t) | 199 | if (body.displayName !== undefined) userAccount.name = body.displayName |
200 | if (body.description !== undefined) userAccount.description = body.description | ||
201 | await userAccount.save({ transaction: t }) | ||
207 | 202 | ||
208 | auditLogger.update(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON({})), oldUserAuditView) | 203 | await sendUpdateActor(userAccount, t) |
204 | } | ||
209 | }) | 205 | }) |
210 | 206 | ||
211 | if (sendVerificationEmail === true) { | 207 | if (sendVerificationEmail === true) { |
@@ -218,13 +214,10 @@ async function updateMe (req: express.Request, res: express.Response) { | |||
218 | async function updateMyAvatar (req: express.Request, res: express.Response) { | 214 | async function updateMyAvatar (req: express.Request, res: express.Response) { |
219 | const avatarPhysicalFile = req.files[ 'avatarfile' ][ 0 ] | 215 | const avatarPhysicalFile = req.files[ 'avatarfile' ][ 0 ] |
220 | const user = res.locals.oauth.token.user | 216 | const user = res.locals.oauth.token.user |
221 | const oldUserAuditView = new UserAuditView(user.toFormattedJSON({})) | ||
222 | 217 | ||
223 | const userAccount = await AccountModel.load(user.Account.id) | 218 | const userAccount = await AccountModel.load(user.Account.id) |
224 | 219 | ||
225 | const avatar = await updateActorAvatarFile(avatarPhysicalFile, userAccount) | 220 | const avatar = await updateActorAvatarFile(avatarPhysicalFile, userAccount) |
226 | 221 | ||
227 | auditLogger.update(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON({})), oldUserAuditView) | ||
228 | |||
229 | return res.json({ avatar: avatar.toFormattedJSON() }) | 222 | return res.json({ avatar: avatar.toFormattedJSON() }) |
230 | } | 223 | } |
diff --git a/server/controllers/api/users/my-history.ts b/server/controllers/api/users/my-history.ts index 7025c0ff1..4da1f3496 100644 --- a/server/controllers/api/users/my-history.ts +++ b/server/controllers/api/users/my-history.ts | |||
@@ -7,7 +7,6 @@ import { | |||
7 | setDefaultPagination, | 7 | setDefaultPagination, |
8 | userHistoryRemoveValidator | 8 | userHistoryRemoveValidator |
9 | } from '../../../middlewares' | 9 | } from '../../../middlewares' |
10 | import { UserModel } from '../../../models/account/user' | ||
11 | import { getFormattedObjects } from '../../../helpers/utils' | 10 | import { getFormattedObjects } from '../../../helpers/utils' |
12 | import { UserVideoHistoryModel } from '../../../models/account/user-video-history' | 11 | import { UserVideoHistoryModel } from '../../../models/account/user-video-history' |
13 | import { sequelizeTypescript } from '../../../initializers' | 12 | import { sequelizeTypescript } from '../../../initializers' |
diff --git a/server/controllers/api/users/my-notifications.ts b/server/controllers/api/users/my-notifications.ts index f146284e4..017f5219e 100644 --- a/server/controllers/api/users/my-notifications.ts +++ b/server/controllers/api/users/my-notifications.ts | |||
@@ -76,7 +76,8 @@ async function updateNotificationSettings (req: express.Request, res: express.Re | |||
76 | newFollow: body.newFollow, | 76 | newFollow: body.newFollow, |
77 | newUserRegistration: body.newUserRegistration, | 77 | newUserRegistration: body.newUserRegistration, |
78 | commentMention: body.commentMention, | 78 | commentMention: body.commentMention, |
79 | newInstanceFollower: body.newInstanceFollower | 79 | newInstanceFollower: body.newInstanceFollower, |
80 | autoInstanceFollowing: body.autoInstanceFollowing | ||
80 | } | 81 | } |
81 | 82 | ||
82 | await UserNotificationSettingModel.update(values, query) | 83 | await UserNotificationSettingModel.update(values, query) |
diff --git a/server/controllers/api/video-channel.ts b/server/controllers/api/video-channel.ts index 81a03a62b..acc5b2987 100644 --- a/server/controllers/api/video-channel.ts +++ b/server/controllers/api/video-channel.ts | |||
@@ -19,7 +19,7 @@ import { VideoChannelModel } from '../../models/video/video-channel' | |||
19 | import { videoChannelsNameWithHostValidator, videosSortValidator } from '../../middlewares/validators' | 19 | import { videoChannelsNameWithHostValidator, videosSortValidator } from '../../middlewares/validators' |
20 | import { sendUpdateActor } from '../../lib/activitypub/send' | 20 | import { sendUpdateActor } from '../../lib/activitypub/send' |
21 | import { VideoChannelCreate, VideoChannelUpdate } from '../../../shared' | 21 | import { VideoChannelCreate, VideoChannelUpdate } from '../../../shared' |
22 | import { createVideoChannel, federateAllVideosOfChannel } from '../../lib/video-channel' | 22 | import { createLocalVideoChannel, federateAllVideosOfChannel } from '../../lib/video-channel' |
23 | import { buildNSFWFilter, createReqFiles, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils' | 23 | import { buildNSFWFilter, createReqFiles, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils' |
24 | import { setAsyncActorKeys } from '../../lib/activitypub' | 24 | import { setAsyncActorKeys } from '../../lib/activitypub' |
25 | import { AccountModel } from '../../models/account/account' | 25 | import { AccountModel } from '../../models/account/account' |
@@ -35,6 +35,7 @@ import { VideoPlaylistModel } from '../../models/video/video-playlist' | |||
35 | import { commonVideoPlaylistFiltersValidator } from '../../middlewares/validators/videos/video-playlists' | 35 | import { commonVideoPlaylistFiltersValidator } from '../../middlewares/validators/videos/video-playlists' |
36 | import { CONFIG } from '../../initializers/config' | 36 | import { CONFIG } from '../../initializers/config' |
37 | import { sequelizeTypescript } from '../../initializers/database' | 37 | import { sequelizeTypescript } from '../../initializers/database' |
38 | import { MChannelAccountDefault } from '@server/typings/models' | ||
38 | 39 | ||
39 | const auditLogger = auditLoggerFactory('channels') | 40 | const auditLogger = auditLoggerFactory('channels') |
40 | const reqAvatarFile = createReqFiles([ 'avatarfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.TMP_DIR }) | 41 | const reqAvatarFile = createReqFiles([ 'avatarfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.TMP_DIR }) |
@@ -136,10 +137,10 @@ async function updateVideoChannelAvatar (req: express.Request, res: express.Resp | |||
136 | async function addVideoChannel (req: express.Request, res: express.Response) { | 137 | async function addVideoChannel (req: express.Request, res: express.Response) { |
137 | const videoChannelInfo: VideoChannelCreate = req.body | 138 | const videoChannelInfo: VideoChannelCreate = req.body |
138 | 139 | ||
139 | const videoChannelCreated: VideoChannelModel = await sequelizeTypescript.transaction(async t => { | 140 | const videoChannelCreated = await sequelizeTypescript.transaction(async t => { |
140 | const account = await AccountModel.load(res.locals.oauth.token.User.Account.id, t) | 141 | const account = await AccountModel.load(res.locals.oauth.token.User.Account.id, t) |
141 | 142 | ||
142 | return createVideoChannel(videoChannelInfo, account, t) | 143 | return createLocalVideoChannel(videoChannelInfo, account, t) |
143 | }) | 144 | }) |
144 | 145 | ||
145 | setAsyncActorKeys(videoChannelCreated.Actor) | 146 | setAsyncActorKeys(videoChannelCreated.Actor) |
@@ -181,7 +182,7 @@ async function updateVideoChannel (req: express.Request, res: express.Response) | |||
181 | } | 182 | } |
182 | } | 183 | } |
183 | 184 | ||
184 | const videoChannelInstanceUpdated = await videoChannelInstance.save(sequelizeOptions) | 185 | const videoChannelInstanceUpdated = await videoChannelInstance.save(sequelizeOptions) as MChannelAccountDefault |
185 | await sendUpdateActor(videoChannelInstanceUpdated, t) | 186 | await sendUpdateActor(videoChannelInstanceUpdated, t) |
186 | 187 | ||
187 | auditLogger.update( | 188 | auditLogger.update( |
diff --git a/server/controllers/api/video-playlist.ts b/server/controllers/api/video-playlist.ts index bd454f553..d9f0ff925 100644 --- a/server/controllers/api/video-playlist.ts +++ b/server/controllers/api/video-playlist.ts | |||
@@ -40,7 +40,7 @@ import { JobQueue } from '../../lib/job-queue' | |||
40 | import { CONFIG } from '../../initializers/config' | 40 | import { CONFIG } from '../../initializers/config' |
41 | import { sequelizeTypescript } from '../../initializers/database' | 41 | import { sequelizeTypescript } from '../../initializers/database' |
42 | import { createPlaylistMiniatureFromExisting } from '../../lib/thumbnail' | 42 | import { createPlaylistMiniatureFromExisting } from '../../lib/thumbnail' |
43 | import { VideoModel } from '../../models/video/video' | 43 | import { MVideoPlaylistFull, MVideoPlaylistThumbnail, MVideoThumbnail } from '@server/typings/models' |
44 | 44 | ||
45 | const reqThumbnailFile = createReqFiles([ 'thumbnailfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { thumbnailfile: CONFIG.STORAGE.TMP_DIR }) | 45 | const reqThumbnailFile = createReqFiles([ 'thumbnailfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { thumbnailfile: CONFIG.STORAGE.TMP_DIR }) |
46 | 46 | ||
@@ -58,7 +58,7 @@ videoPlaylistRouter.get('/', | |||
58 | ) | 58 | ) |
59 | 59 | ||
60 | videoPlaylistRouter.get('/:playlistId', | 60 | videoPlaylistRouter.get('/:playlistId', |
61 | asyncMiddleware(videoPlaylistsGetValidator), | 61 | asyncMiddleware(videoPlaylistsGetValidator('summary')), |
62 | getVideoPlaylist | 62 | getVideoPlaylist |
63 | ) | 63 | ) |
64 | 64 | ||
@@ -83,7 +83,7 @@ videoPlaylistRouter.delete('/:playlistId', | |||
83 | ) | 83 | ) |
84 | 84 | ||
85 | videoPlaylistRouter.get('/:playlistId/videos', | 85 | videoPlaylistRouter.get('/:playlistId/videos', |
86 | asyncMiddleware(videoPlaylistsGetValidator), | 86 | asyncMiddleware(videoPlaylistsGetValidator('summary')), |
87 | paginationValidator, | 87 | paginationValidator, |
88 | setDefaultPagination, | 88 | setDefaultPagination, |
89 | optionalAuthenticate, | 89 | optionalAuthenticate, |
@@ -140,7 +140,7 @@ async function listVideoPlaylists (req: express.Request, res: express.Response) | |||
140 | } | 140 | } |
141 | 141 | ||
142 | function getVideoPlaylist (req: express.Request, res: express.Response) { | 142 | function getVideoPlaylist (req: express.Request, res: express.Response) { |
143 | const videoPlaylist = res.locals.videoPlaylist | 143 | const videoPlaylist = res.locals.videoPlaylistSummary |
144 | 144 | ||
145 | if (videoPlaylist.isOutdated()) { | 145 | if (videoPlaylist.isOutdated()) { |
146 | JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'video-playlist', url: videoPlaylist.url } }) | 146 | JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'video-playlist', url: videoPlaylist.url } }) |
@@ -159,7 +159,7 @@ async function addVideoPlaylist (req: express.Request, res: express.Response) { | |||
159 | description: videoPlaylistInfo.description, | 159 | description: videoPlaylistInfo.description, |
160 | privacy: videoPlaylistInfo.privacy || VideoPlaylistPrivacy.PRIVATE, | 160 | privacy: videoPlaylistInfo.privacy || VideoPlaylistPrivacy.PRIVATE, |
161 | ownerAccountId: user.Account.id | 161 | ownerAccountId: user.Account.id |
162 | }) | 162 | }) as MVideoPlaylistFull |
163 | 163 | ||
164 | videoPlaylist.url = getVideoPlaylistActivityPubUrl(videoPlaylist) // We use the UUID, so set the URL after building the object | 164 | videoPlaylist.url = getVideoPlaylistActivityPubUrl(videoPlaylist) // We use the UUID, so set the URL after building the object |
165 | 165 | ||
@@ -175,8 +175,8 @@ async function addVideoPlaylist (req: express.Request, res: express.Response) { | |||
175 | ? await createPlaylistMiniatureFromExisting(thumbnailField[0].path, videoPlaylist, false) | 175 | ? await createPlaylistMiniatureFromExisting(thumbnailField[0].path, videoPlaylist, false) |
176 | : undefined | 176 | : undefined |
177 | 177 | ||
178 | const videoPlaylistCreated: VideoPlaylistModel = await sequelizeTypescript.transaction(async t => { | 178 | const videoPlaylistCreated = await sequelizeTypescript.transaction(async t => { |
179 | const videoPlaylistCreated = await videoPlaylist.save({ transaction: t }) | 179 | const videoPlaylistCreated = await videoPlaylist.save({ transaction: t }) as MVideoPlaylistFull |
180 | 180 | ||
181 | if (thumbnailModel) { | 181 | if (thumbnailModel) { |
182 | thumbnailModel.automaticallyGenerated = false | 182 | thumbnailModel.automaticallyGenerated = false |
@@ -201,7 +201,7 @@ async function addVideoPlaylist (req: express.Request, res: express.Response) { | |||
201 | } | 201 | } |
202 | 202 | ||
203 | async function updateVideoPlaylist (req: express.Request, res: express.Response) { | 203 | async function updateVideoPlaylist (req: express.Request, res: express.Response) { |
204 | const videoPlaylistInstance = res.locals.videoPlaylist | 204 | const videoPlaylistInstance = res.locals.videoPlaylistFull |
205 | const videoPlaylistFieldsSave = videoPlaylistInstance.toJSON() | 205 | const videoPlaylistFieldsSave = videoPlaylistInstance.toJSON() |
206 | const videoPlaylistInfoToUpdate = req.body as VideoPlaylistUpdate | 206 | const videoPlaylistInfoToUpdate = req.body as VideoPlaylistUpdate |
207 | 207 | ||
@@ -275,7 +275,7 @@ async function updateVideoPlaylist (req: express.Request, res: express.Response) | |||
275 | } | 275 | } |
276 | 276 | ||
277 | async function removeVideoPlaylist (req: express.Request, res: express.Response) { | 277 | async function removeVideoPlaylist (req: express.Request, res: express.Response) { |
278 | const videoPlaylistInstance = res.locals.videoPlaylist | 278 | const videoPlaylistInstance = res.locals.videoPlaylistSummary |
279 | 279 | ||
280 | await sequelizeTypescript.transaction(async t => { | 280 | await sequelizeTypescript.transaction(async t => { |
281 | await videoPlaylistInstance.destroy({ transaction: t }) | 281 | await videoPlaylistInstance.destroy({ transaction: t }) |
@@ -290,10 +290,10 @@ async function removeVideoPlaylist (req: express.Request, res: express.Response) | |||
290 | 290 | ||
291 | async function addVideoInPlaylist (req: express.Request, res: express.Response) { | 291 | async function addVideoInPlaylist (req: express.Request, res: express.Response) { |
292 | const body: VideoPlaylistElementCreate = req.body | 292 | const body: VideoPlaylistElementCreate = req.body |
293 | const videoPlaylist = res.locals.videoPlaylist | 293 | const videoPlaylist = res.locals.videoPlaylistFull |
294 | const video = res.locals.video | 294 | const video = res.locals.onlyVideo |
295 | 295 | ||
296 | const playlistElement: VideoPlaylistElementModel = await sequelizeTypescript.transaction(async t => { | 296 | const playlistElement = await sequelizeTypescript.transaction(async t => { |
297 | const position = await VideoPlaylistElementModel.getNextPositionOf(videoPlaylist.id, t) | 297 | const position = await VideoPlaylistElementModel.getNextPositionOf(videoPlaylist.id, t) |
298 | 298 | ||
299 | const playlistElement = await VideoPlaylistElementModel.create({ | 299 | const playlistElement = await VideoPlaylistElementModel.create({ |
@@ -330,7 +330,7 @@ async function addVideoInPlaylist (req: express.Request, res: express.Response) | |||
330 | 330 | ||
331 | async function updateVideoPlaylistElement (req: express.Request, res: express.Response) { | 331 | async function updateVideoPlaylistElement (req: express.Request, res: express.Response) { |
332 | const body: VideoPlaylistElementUpdate = req.body | 332 | const body: VideoPlaylistElementUpdate = req.body |
333 | const videoPlaylist = res.locals.videoPlaylist | 333 | const videoPlaylist = res.locals.videoPlaylistFull |
334 | const videoPlaylistElement = res.locals.videoPlaylistElement | 334 | const videoPlaylistElement = res.locals.videoPlaylistElement |
335 | 335 | ||
336 | const playlistElement: VideoPlaylistElementModel = await sequelizeTypescript.transaction(async t => { | 336 | const playlistElement: VideoPlaylistElementModel = await sequelizeTypescript.transaction(async t => { |
@@ -354,7 +354,7 @@ async function updateVideoPlaylistElement (req: express.Request, res: express.Re | |||
354 | 354 | ||
355 | async function removeVideoFromPlaylist (req: express.Request, res: express.Response) { | 355 | async function removeVideoFromPlaylist (req: express.Request, res: express.Response) { |
356 | const videoPlaylistElement = res.locals.videoPlaylistElement | 356 | const videoPlaylistElement = res.locals.videoPlaylistElement |
357 | const videoPlaylist = res.locals.videoPlaylist | 357 | const videoPlaylist = res.locals.videoPlaylistFull |
358 | const positionToDelete = videoPlaylistElement.position | 358 | const positionToDelete = videoPlaylistElement.position |
359 | 359 | ||
360 | await sequelizeTypescript.transaction(async t => { | 360 | await sequelizeTypescript.transaction(async t => { |
@@ -381,7 +381,7 @@ async function removeVideoFromPlaylist (req: express.Request, res: express.Respo | |||
381 | } | 381 | } |
382 | 382 | ||
383 | async function reorderVideosPlaylist (req: express.Request, res: express.Response) { | 383 | async function reorderVideosPlaylist (req: express.Request, res: express.Response) { |
384 | const videoPlaylist = res.locals.videoPlaylist | 384 | const videoPlaylist = res.locals.videoPlaylistFull |
385 | const body: VideoPlaylistReorder = req.body | 385 | const body: VideoPlaylistReorder = req.body |
386 | 386 | ||
387 | const start: number = body.startPosition | 387 | const start: number = body.startPosition |
@@ -434,7 +434,7 @@ async function reorderVideosPlaylist (req: express.Request, res: express.Respons | |||
434 | } | 434 | } |
435 | 435 | ||
436 | async function getVideoPlaylistVideos (req: express.Request, res: express.Response) { | 436 | async function getVideoPlaylistVideos (req: express.Request, res: express.Response) { |
437 | const videoPlaylistInstance = res.locals.videoPlaylist | 437 | const videoPlaylistInstance = res.locals.videoPlaylistSummary |
438 | const user = res.locals.oauth ? res.locals.oauth.token.User : undefined | 438 | const user = res.locals.oauth ? res.locals.oauth.token.User : undefined |
439 | const server = await getServerActor() | 439 | const server = await getServerActor() |
440 | 440 | ||
@@ -453,7 +453,7 @@ async function getVideoPlaylistVideos (req: express.Request, res: express.Respon | |||
453 | return res.json(getFormattedObjects(resultList.data, resultList.total, options)) | 453 | return res.json(getFormattedObjects(resultList.data, resultList.total, options)) |
454 | } | 454 | } |
455 | 455 | ||
456 | async function regeneratePlaylistThumbnail (videoPlaylist: VideoPlaylistModel) { | 456 | async function regeneratePlaylistThumbnail (videoPlaylist: MVideoPlaylistThumbnail) { |
457 | await videoPlaylist.Thumbnail.destroy() | 457 | await videoPlaylist.Thumbnail.destroy() |
458 | videoPlaylist.Thumbnail = null | 458 | videoPlaylist.Thumbnail = null |
459 | 459 | ||
@@ -461,7 +461,7 @@ async function regeneratePlaylistThumbnail (videoPlaylist: VideoPlaylistModel) { | |||
461 | if (firstElement) await generateThumbnailForPlaylist(videoPlaylist, firstElement.Video) | 461 | if (firstElement) await generateThumbnailForPlaylist(videoPlaylist, firstElement.Video) |
462 | } | 462 | } |
463 | 463 | ||
464 | async function generateThumbnailForPlaylist (videoPlaylist: VideoPlaylistModel, video: VideoModel) { | 464 | async function generateThumbnailForPlaylist (videoPlaylist: MVideoPlaylistThumbnail, video: MVideoThumbnail) { |
465 | logger.info('Generating default thumbnail to playlist %s.', videoPlaylist.url) | 465 | logger.info('Generating default thumbnail to playlist %s.', videoPlaylist.url) |
466 | 466 | ||
467 | const inputPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, video.getMiniature().filename) | 467 | const inputPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, video.getMiniature().filename) |
diff --git a/server/controllers/api/videos/abuse.ts b/server/controllers/api/videos/abuse.ts index 77808466c..4ae899b7e 100644 --- a/server/controllers/api/videos/abuse.ts +++ b/server/controllers/api/videos/abuse.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { UserRight, VideoAbuseCreate, VideoAbuseState } from '../../../../shared' | 2 | import { UserRight, VideoAbuseCreate, VideoAbuseState } from '../../../../shared' |
3 | import { logger } from '../../../helpers/logger' | 3 | import { logger } from '../../../helpers/logger' |
4 | import { getFormattedObjects } from '../../../helpers/utils' | 4 | import { getFormattedObjects, getServerActor } from '../../../helpers/utils' |
5 | import { sequelizeTypescript } from '../../../initializers' | 5 | import { sequelizeTypescript } from '../../../initializers' |
6 | import { | 6 | import { |
7 | asyncMiddleware, | 7 | asyncMiddleware, |
@@ -21,6 +21,7 @@ import { VideoAbuseModel } from '../../../models/video/video-abuse' | |||
21 | import { auditLoggerFactory, VideoAbuseAuditView } from '../../../helpers/audit-logger' | 21 | import { auditLoggerFactory, VideoAbuseAuditView } from '../../../helpers/audit-logger' |
22 | import { Notifier } from '../../../lib/notifier' | 22 | import { Notifier } from '../../../lib/notifier' |
23 | import { sendVideoAbuse } from '../../../lib/activitypub/send/send-flag' | 23 | import { sendVideoAbuse } from '../../../lib/activitypub/send/send-flag' |
24 | import { MVideoAbuseAccountVideo } from '../../../typings/models/video' | ||
24 | 25 | ||
25 | const auditLogger = auditLoggerFactory('abuse') | 26 | const auditLogger = auditLoggerFactory('abuse') |
26 | const abuseVideoRouter = express.Router() | 27 | const abuseVideoRouter = express.Router() |
@@ -61,7 +62,16 @@ export { | |||
61 | // --------------------------------------------------------------------------- | 62 | // --------------------------------------------------------------------------- |
62 | 63 | ||
63 | async function listVideoAbuses (req: express.Request, res: express.Response) { | 64 | async function listVideoAbuses (req: express.Request, res: express.Response) { |
64 | const resultList = await VideoAbuseModel.listForApi(req.query.start, req.query.count, req.query.sort) | 65 | const user = res.locals.oauth.token.user |
66 | const serverActor = await getServerActor() | ||
67 | |||
68 | const resultList = await VideoAbuseModel.listForApi({ | ||
69 | start: req.query.start, | ||
70 | count: req.query.count, | ||
71 | sort: req.query.sort, | ||
72 | serverAccountId: serverActor.Account.id, | ||
73 | user | ||
74 | }) | ||
65 | 75 | ||
66 | return res.json(getFormattedObjects(resultList.data, resultList.total)) | 76 | return res.json(getFormattedObjects(resultList.data, resultList.total)) |
67 | } | 77 | } |
@@ -94,10 +104,10 @@ async function deleteVideoAbuse (req: express.Request, res: express.Response) { | |||
94 | } | 104 | } |
95 | 105 | ||
96 | async function reportVideoAbuse (req: express.Request, res: express.Response) { | 106 | async function reportVideoAbuse (req: express.Request, res: express.Response) { |
97 | const videoInstance = res.locals.video | 107 | const videoInstance = res.locals.videoAll |
98 | const body: VideoAbuseCreate = req.body | 108 | const body: VideoAbuseCreate = req.body |
99 | 109 | ||
100 | const videoAbuse: VideoAbuseModel = await sequelizeTypescript.transaction(async t => { | 110 | const videoAbuse = await sequelizeTypescript.transaction(async t => { |
101 | const reporterAccount = await AccountModel.load(res.locals.oauth.token.User.Account.id, t) | 111 | const reporterAccount = await AccountModel.load(res.locals.oauth.token.User.Account.id, t) |
102 | 112 | ||
103 | const abuseToCreate = { | 113 | const abuseToCreate = { |
@@ -107,7 +117,7 @@ async function reportVideoAbuse (req: express.Request, res: express.Response) { | |||
107 | state: VideoAbuseState.PENDING | 117 | state: VideoAbuseState.PENDING |
108 | } | 118 | } |
109 | 119 | ||
110 | const videoAbuseInstance = await VideoAbuseModel.create(abuseToCreate, { transaction: t }) | 120 | const videoAbuseInstance: MVideoAbuseAccountVideo = await VideoAbuseModel.create(abuseToCreate, { transaction: t }) |
111 | videoAbuseInstance.Video = videoInstance | 121 | videoAbuseInstance.Video = videoInstance |
112 | videoAbuseInstance.Account = reporterAccount | 122 | videoAbuseInstance.Account = reporterAccount |
113 | 123 | ||
diff --git a/server/controllers/api/videos/blacklist.ts b/server/controllers/api/videos/blacklist.ts index 9ff494def..2a667480d 100644 --- a/server/controllers/api/videos/blacklist.ts +++ b/server/controllers/api/videos/blacklist.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { VideoBlacklist, UserRight, VideoBlacklistCreate, VideoBlacklistType } from '../../../../shared' | 2 | import { UserRight, VideoBlacklistCreate, VideoBlacklistType } from '../../../../shared' |
3 | import { logger } from '../../../helpers/logger' | 3 | import { logger } from '../../../helpers/logger' |
4 | import { getFormattedObjects } from '../../../helpers/utils' | 4 | import { getFormattedObjects } from '../../../helpers/utils' |
5 | import { | 5 | import { |
@@ -11,15 +11,16 @@ import { | |||
11 | setBlacklistSort, | 11 | setBlacklistSort, |
12 | setDefaultPagination, | 12 | setDefaultPagination, |
13 | videosBlacklistAddValidator, | 13 | videosBlacklistAddValidator, |
14 | videosBlacklistFiltersValidator, | ||
14 | videosBlacklistRemoveValidator, | 15 | videosBlacklistRemoveValidator, |
15 | videosBlacklistUpdateValidator, | 16 | videosBlacklistUpdateValidator |
16 | videosBlacklistFiltersValidator | ||
17 | } from '../../../middlewares' | 17 | } from '../../../middlewares' |
18 | import { VideoBlacklistModel } from '../../../models/video/video-blacklist' | 18 | import { VideoBlacklistModel } from '../../../models/video/video-blacklist' |
19 | import { sequelizeTypescript } from '../../../initializers' | 19 | import { sequelizeTypescript } from '../../../initializers' |
20 | import { Notifier } from '../../../lib/notifier' | 20 | import { Notifier } from '../../../lib/notifier' |
21 | import { sendDeleteVideo } from '../../../lib/activitypub/send' | 21 | import { sendDeleteVideo } from '../../../lib/activitypub/send' |
22 | import { federateVideoIfNeeded } from '../../../lib/activitypub' | 22 | import { federateVideoIfNeeded } from '../../../lib/activitypub' |
23 | import { MVideoBlacklistVideo } from '@server/typings/models' | ||
23 | 24 | ||
24 | const blacklistRouter = express.Router() | 25 | const blacklistRouter = express.Router() |
25 | 26 | ||
@@ -64,7 +65,7 @@ export { | |||
64 | // --------------------------------------------------------------------------- | 65 | // --------------------------------------------------------------------------- |
65 | 66 | ||
66 | async function addVideoToBlacklist (req: express.Request, res: express.Response) { | 67 | async function addVideoToBlacklist (req: express.Request, res: express.Response) { |
67 | const videoInstance = res.locals.video | 68 | const videoInstance = res.locals.videoAll |
68 | const body: VideoBlacklistCreate = req.body | 69 | const body: VideoBlacklistCreate = req.body |
69 | 70 | ||
70 | const toCreate = { | 71 | const toCreate = { |
@@ -74,7 +75,7 @@ async function addVideoToBlacklist (req: express.Request, res: express.Response) | |||
74 | type: VideoBlacklistType.MANUAL | 75 | type: VideoBlacklistType.MANUAL |
75 | } | 76 | } |
76 | 77 | ||
77 | const blacklist = await VideoBlacklistModel.create(toCreate) | 78 | const blacklist: MVideoBlacklistVideo = await VideoBlacklistModel.create(toCreate) |
78 | blacklist.Video = videoInstance | 79 | blacklist.Video = videoInstance |
79 | 80 | ||
80 | if (body.unfederate === true) { | 81 | if (body.unfederate === true) { |
@@ -83,7 +84,7 @@ async function addVideoToBlacklist (req: express.Request, res: express.Response) | |||
83 | 84 | ||
84 | Notifier.Instance.notifyOnVideoBlacklist(blacklist) | 85 | Notifier.Instance.notifyOnVideoBlacklist(blacklist) |
85 | 86 | ||
86 | logger.info('Video %s blacklisted.', res.locals.video.uuid) | 87 | logger.info('Video %s blacklisted.', videoInstance.uuid) |
87 | 88 | ||
88 | return res.type('json').status(204).end() | 89 | return res.type('json').status(204).end() |
89 | } | 90 | } |
@@ -108,7 +109,7 @@ async function listBlacklist (req: express.Request, res: express.Response) { | |||
108 | 109 | ||
109 | async function removeVideoFromBlacklistController (req: express.Request, res: express.Response) { | 110 | async function removeVideoFromBlacklistController (req: express.Request, res: express.Response) { |
110 | const videoBlacklist = res.locals.videoBlacklist | 111 | const videoBlacklist = res.locals.videoBlacklist |
111 | const video = res.locals.video | 112 | const video = res.locals.videoAll |
112 | 113 | ||
113 | const videoBlacklistType = await sequelizeTypescript.transaction(async t => { | 114 | const videoBlacklistType = await sequelizeTypescript.transaction(async t => { |
114 | const unfederated = videoBlacklist.unfederated | 115 | const unfederated = videoBlacklist.unfederated |
@@ -135,7 +136,7 @@ async function removeVideoFromBlacklistController (req: express.Request, res: ex | |||
135 | Notifier.Instance.notifyOnNewVideoIfNeeded(video) | 136 | Notifier.Instance.notifyOnNewVideoIfNeeded(video) |
136 | } | 137 | } |
137 | 138 | ||
138 | logger.info('Video %s removed from blacklist.', res.locals.video.uuid) | 139 | logger.info('Video %s removed from blacklist.', video.uuid) |
139 | 140 | ||
140 | return res.type('json').status(204).end() | 141 | return res.type('json').status(204).end() |
141 | } | 142 | } |
diff --git a/server/controllers/api/videos/captions.ts b/server/controllers/api/videos/captions.ts index 44c255232..37481d12f 100644 --- a/server/controllers/api/videos/captions.ts +++ b/server/controllers/api/videos/captions.ts | |||
@@ -10,6 +10,7 @@ import { federateVideoIfNeeded } from '../../../lib/activitypub' | |||
10 | import { moveAndProcessCaptionFile } from '../../../helpers/captions-utils' | 10 | import { moveAndProcessCaptionFile } from '../../../helpers/captions-utils' |
11 | import { CONFIG } from '../../../initializers/config' | 11 | import { CONFIG } from '../../../initializers/config' |
12 | import { sequelizeTypescript } from '../../../initializers/database' | 12 | import { sequelizeTypescript } from '../../../initializers/database' |
13 | import { MVideoCaptionVideo } from '@server/typings/models' | ||
13 | 14 | ||
14 | const reqVideoCaptionAdd = createReqFiles( | 15 | const reqVideoCaptionAdd = createReqFiles( |
15 | [ 'captionfile' ], | 16 | [ 'captionfile' ], |
@@ -46,19 +47,19 @@ export { | |||
46 | // --------------------------------------------------------------------------- | 47 | // --------------------------------------------------------------------------- |
47 | 48 | ||
48 | async function listVideoCaptions (req: express.Request, res: express.Response) { | 49 | async function listVideoCaptions (req: express.Request, res: express.Response) { |
49 | const data = await VideoCaptionModel.listVideoCaptions(res.locals.video.id) | 50 | const data = await VideoCaptionModel.listVideoCaptions(res.locals.videoId.id) |
50 | 51 | ||
51 | return res.json(getFormattedObjects(data, data.length)) | 52 | return res.json(getFormattedObjects(data, data.length)) |
52 | } | 53 | } |
53 | 54 | ||
54 | async function addVideoCaption (req: express.Request, res: express.Response) { | 55 | async function addVideoCaption (req: express.Request, res: express.Response) { |
55 | const videoCaptionPhysicalFile = req.files['captionfile'][0] | 56 | const videoCaptionPhysicalFile = req.files['captionfile'][0] |
56 | const video = res.locals.video | 57 | const video = res.locals.videoAll |
57 | 58 | ||
58 | const videoCaption = new VideoCaptionModel({ | 59 | const videoCaption = new VideoCaptionModel({ |
59 | videoId: video.id, | 60 | videoId: video.id, |
60 | language: req.params.captionLanguage | 61 | language: req.params.captionLanguage |
61 | }) | 62 | }) as MVideoCaptionVideo |
62 | videoCaption.Video = video | 63 | videoCaption.Video = video |
63 | 64 | ||
64 | // Move physical file | 65 | // Move physical file |
@@ -75,7 +76,7 @@ async function addVideoCaption (req: express.Request, res: express.Response) { | |||
75 | } | 76 | } |
76 | 77 | ||
77 | async function deleteVideoCaption (req: express.Request, res: express.Response) { | 78 | async function deleteVideoCaption (req: express.Request, res: express.Response) { |
78 | const video = res.locals.video | 79 | const video = res.locals.videoAll |
79 | const videoCaption = res.locals.videoCaption | 80 | const videoCaption = res.locals.videoCaption |
80 | 81 | ||
81 | await sequelizeTypescript.transaction(async t => { | 82 | await sequelizeTypescript.transaction(async t => { |
diff --git a/server/controllers/api/videos/comment.ts b/server/controllers/api/videos/comment.ts index bc6d81a7c..b2b06b170 100644 --- a/server/controllers/api/videos/comment.ts +++ b/server/controllers/api/videos/comment.ts | |||
@@ -27,9 +27,6 @@ import { auditLoggerFactory, CommentAuditView, getAuditIdFromRes } from '../../. | |||
27 | import { AccountModel } from '../../../models/account/account' | 27 | import { AccountModel } from '../../../models/account/account' |
28 | import { Notifier } from '../../../lib/notifier' | 28 | import { Notifier } from '../../../lib/notifier' |
29 | import { Hooks } from '../../../lib/plugins/hooks' | 29 | import { Hooks } from '../../../lib/plugins/hooks' |
30 | import { ActorModel } from '../../../models/activitypub/actor' | ||
31 | import { VideoChannelModel } from '../../../models/video/video-channel' | ||
32 | import { VideoModel } from '../../../models/video/video' | ||
33 | import { sendDeleteVideoComment } from '../../../lib/activitypub/send' | 30 | import { sendDeleteVideoComment } from '../../../lib/activitypub/send' |
34 | 31 | ||
35 | const auditLogger = auditLoggerFactory('comments') | 32 | const auditLogger = auditLoggerFactory('comments') |
@@ -75,7 +72,7 @@ export { | |||
75 | // --------------------------------------------------------------------------- | 72 | // --------------------------------------------------------------------------- |
76 | 73 | ||
77 | async function listVideoThreads (req: express.Request, res: express.Response) { | 74 | async function listVideoThreads (req: express.Request, res: express.Response) { |
78 | const video = res.locals.video | 75 | const video = res.locals.onlyVideo |
79 | const user = res.locals.oauth ? res.locals.oauth.token.User : undefined | 76 | const user = res.locals.oauth ? res.locals.oauth.token.User : undefined |
80 | 77 | ||
81 | let resultList: ResultList<VideoCommentModel> | 78 | let resultList: ResultList<VideoCommentModel> |
@@ -86,7 +83,7 @@ async function listVideoThreads (req: express.Request, res: express.Response) { | |||
86 | start: req.query.start, | 83 | start: req.query.start, |
87 | count: req.query.count, | 84 | count: req.query.count, |
88 | sort: req.query.sort, | 85 | sort: req.query.sort, |
89 | user: user | 86 | user |
90 | }, 'filter:api.video-threads.list.params') | 87 | }, 'filter:api.video-threads.list.params') |
91 | 88 | ||
92 | resultList = await Hooks.wrapPromiseFun( | 89 | resultList = await Hooks.wrapPromiseFun( |
@@ -105,7 +102,7 @@ async function listVideoThreads (req: express.Request, res: express.Response) { | |||
105 | } | 102 | } |
106 | 103 | ||
107 | async function listVideoThreadComments (req: express.Request, res: express.Response) { | 104 | async function listVideoThreadComments (req: express.Request, res: express.Response) { |
108 | const video = res.locals.video | 105 | const video = res.locals.onlyVideo |
109 | const user = res.locals.oauth ? res.locals.oauth.token.User : undefined | 106 | const user = res.locals.oauth ? res.locals.oauth.token.User : undefined |
110 | 107 | ||
111 | let resultList: ResultList<VideoCommentModel> | 108 | let resultList: ResultList<VideoCommentModel> |
@@ -141,7 +138,7 @@ async function addVideoCommentThread (req: express.Request, res: express.Respons | |||
141 | return createVideoComment({ | 138 | return createVideoComment({ |
142 | text: videoCommentInfo.text, | 139 | text: videoCommentInfo.text, |
143 | inReplyToComment: null, | 140 | inReplyToComment: null, |
144 | video: res.locals.video, | 141 | video: res.locals.videoAll, |
145 | account | 142 | account |
146 | }, t) | 143 | }, t) |
147 | }) | 144 | }) |
@@ -164,8 +161,8 @@ async function addVideoCommentReply (req: express.Request, res: express.Response | |||
164 | 161 | ||
165 | return createVideoComment({ | 162 | return createVideoComment({ |
166 | text: videoCommentInfo.text, | 163 | text: videoCommentInfo.text, |
167 | inReplyToComment: res.locals.videoComment, | 164 | inReplyToComment: res.locals.videoCommentFull, |
168 | video: res.locals.video, | 165 | video: res.locals.videoAll, |
169 | account | 166 | account |
170 | }, t) | 167 | }, t) |
171 | }) | 168 | }) |
@@ -179,7 +176,7 @@ async function addVideoCommentReply (req: express.Request, res: express.Response | |||
179 | } | 176 | } |
180 | 177 | ||
181 | async function removeVideoComment (req: express.Request, res: express.Response) { | 178 | async function removeVideoComment (req: express.Request, res: express.Response) { |
182 | const videoCommentInstance = res.locals.videoComment | 179 | const videoCommentInstance = res.locals.videoCommentFull |
183 | 180 | ||
184 | await sequelizeTypescript.transaction(async t => { | 181 | await sequelizeTypescript.transaction(async t => { |
185 | await videoCommentInstance.destroy({ transaction: t }) | 182 | await videoCommentInstance.destroy({ transaction: t }) |
diff --git a/server/controllers/api/videos/import.ts b/server/controllers/api/videos/import.ts index 04c9b547b..28ced5836 100644 --- a/server/controllers/api/videos/import.ts +++ b/server/controllers/api/videos/import.ts | |||
@@ -1,6 +1,5 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import * as magnetUtil from 'magnet-uri' | 2 | import * as magnetUtil from 'magnet-uri' |
3 | import 'multer' | ||
4 | import { auditLoggerFactory, getAuditIdFromRes, VideoImportAuditView } from '../../../helpers/audit-logger' | 3 | import { auditLoggerFactory, getAuditIdFromRes, VideoImportAuditView } from '../../../helpers/audit-logger' |
5 | import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videoImportAddValidator } from '../../../middlewares' | 4 | import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videoImportAddValidator } from '../../../middlewares' |
6 | import { MIMETYPES } from '../../../initializers/constants' | 5 | import { MIMETYPES } from '../../../initializers/constants' |
@@ -15,7 +14,6 @@ import { VideoImportModel } from '../../../models/video/video-import' | |||
15 | import { JobQueue } from '../../../lib/job-queue/job-queue' | 14 | import { JobQueue } from '../../../lib/job-queue/job-queue' |
16 | import { join } from 'path' | 15 | import { join } from 'path' |
17 | import { isArray } from '../../../helpers/custom-validators/misc' | 16 | import { isArray } from '../../../helpers/custom-validators/misc' |
18 | import { VideoChannelModel } from '../../../models/video/video-channel' | ||
19 | import * as Bluebird from 'bluebird' | 17 | import * as Bluebird from 'bluebird' |
20 | import * as parseTorrent from 'parse-torrent' | 18 | import * as parseTorrent from 'parse-torrent' |
21 | import { getSecureTorrentName } from '../../../helpers/utils' | 19 | import { getSecureTorrentName } from '../../../helpers/utils' |
@@ -25,8 +23,16 @@ import { CONFIG } from '../../../initializers/config' | |||
25 | import { sequelizeTypescript } from '../../../initializers/database' | 23 | import { sequelizeTypescript } from '../../../initializers/database' |
26 | import { createVideoMiniatureFromExisting } from '../../../lib/thumbnail' | 24 | import { createVideoMiniatureFromExisting } from '../../../lib/thumbnail' |
27 | import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type' | 25 | import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type' |
28 | import { ThumbnailModel } from '../../../models/video/thumbnail' | 26 | import { |
29 | import { UserModel } from '../../../models/account/user' | 27 | MChannelAccountDefault, |
28 | MThumbnail, | ||
29 | MUser, | ||
30 | MVideoAccountDefault, | ||
31 | MVideoTag, | ||
32 | MVideoThumbnailAccountDefault, | ||
33 | MVideoWithBlacklistLight | ||
34 | } from '@server/typings/models' | ||
35 | import { MVideoImport, MVideoImportFormattable } from '@server/typings/models/video/video-import' | ||
30 | 36 | ||
31 | const auditLogger = auditLoggerFactory('video-imports') | 37 | const auditLogger = auditLoggerFactory('video-imports') |
32 | const videoImportsRouter = express.Router() | 38 | const videoImportsRouter = express.Router() |
@@ -184,8 +190,8 @@ function buildVideo (channelId: number, body: VideoImportCreate, importData: You | |||
184 | category: body.category || importData.category, | 190 | category: body.category || importData.category, |
185 | licence: body.licence || importData.licence, | 191 | licence: body.licence || importData.licence, |
186 | language: body.language || undefined, | 192 | language: body.language || undefined, |
187 | commentsEnabled: body.commentsEnabled || true, | 193 | commentsEnabled: body.commentsEnabled !== false, // If the value is not "false", the default is "true" |
188 | downloadEnabled: body.downloadEnabled || true, | 194 | downloadEnabled: body.downloadEnabled !== false, |
189 | waitTranscoding: body.waitTranscoding || false, | 195 | waitTranscoding: body.waitTranscoding || false, |
190 | state: VideoState.TO_IMPORT, | 196 | state: VideoState.TO_IMPORT, |
191 | nsfw: body.nsfw || importData.nsfw || false, | 197 | nsfw: body.nsfw || importData.nsfw || false, |
@@ -225,28 +231,28 @@ async function processPreview (req: express.Request, video: VideoModel) { | |||
225 | } | 231 | } |
226 | 232 | ||
227 | function insertIntoDB (parameters: { | 233 | function insertIntoDB (parameters: { |
228 | video: VideoModel, | 234 | video: MVideoThumbnailAccountDefault, |
229 | thumbnailModel: ThumbnailModel, | 235 | thumbnailModel: MThumbnail, |
230 | previewModel: ThumbnailModel, | 236 | previewModel: MThumbnail, |
231 | videoChannel: VideoChannelModel, | 237 | videoChannel: MChannelAccountDefault, |
232 | tags: string[], | 238 | tags: string[], |
233 | videoImportAttributes: Partial<VideoImportModel>, | 239 | videoImportAttributes: Partial<MVideoImport>, |
234 | user: UserModel | 240 | user: MUser |
235 | }): Bluebird<VideoImportModel> { | 241 | }): Bluebird<MVideoImportFormattable> { |
236 | const { video, thumbnailModel, previewModel, videoChannel, tags, videoImportAttributes, user } = parameters | 242 | const { video, thumbnailModel, previewModel, videoChannel, tags, videoImportAttributes, user } = parameters |
237 | 243 | ||
238 | return sequelizeTypescript.transaction(async t => { | 244 | return sequelizeTypescript.transaction(async t => { |
239 | const sequelizeOptions = { transaction: t } | 245 | const sequelizeOptions = { transaction: t } |
240 | 246 | ||
241 | // Save video object in database | 247 | // Save video object in database |
242 | const videoCreated = await video.save(sequelizeOptions) | 248 | const videoCreated = await video.save(sequelizeOptions) as (MVideoAccountDefault & MVideoWithBlacklistLight & MVideoTag) |
243 | videoCreated.VideoChannel = videoChannel | 249 | videoCreated.VideoChannel = videoChannel |
244 | 250 | ||
245 | if (thumbnailModel) await videoCreated.addAndSaveThumbnail(thumbnailModel, t) | 251 | if (thumbnailModel) await videoCreated.addAndSaveThumbnail(thumbnailModel, t) |
246 | if (previewModel) await videoCreated.addAndSaveThumbnail(previewModel, t) | 252 | if (previewModel) await videoCreated.addAndSaveThumbnail(previewModel, t) |
247 | 253 | ||
248 | await autoBlacklistVideoIfNeeded({ | 254 | await autoBlacklistVideoIfNeeded({ |
249 | video, | 255 | video: videoCreated, |
250 | user, | 256 | user, |
251 | notify: false, | 257 | notify: false, |
252 | isRemote: false, | 258 | isRemote: false, |
@@ -268,7 +274,7 @@ function insertIntoDB (parameters: { | |||
268 | const videoImport = await VideoImportModel.create( | 274 | const videoImport = await VideoImportModel.create( |
269 | Object.assign({ videoId: videoCreated.id }, videoImportAttributes), | 275 | Object.assign({ videoId: videoCreated.id }, videoImportAttributes), |
270 | sequelizeOptions | 276 | sequelizeOptions |
271 | ) | 277 | ) as MVideoImportFormattable |
272 | videoImport.Video = videoCreated | 278 | videoImport.Video = videoCreated |
273 | 279 | ||
274 | return videoImport | 280 | return videoImport |
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts index 155ca4678..19da504c7 100644 --- a/server/controllers/api/videos/index.ts +++ b/server/controllers/api/videos/index.ts | |||
@@ -63,6 +63,7 @@ import { createVideoMiniatureFromExisting, generateVideoMiniature } from '../../ | |||
63 | import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type' | 63 | import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type' |
64 | import { VideoTranscodingPayload } from '../../../lib/job-queue/handlers/video-transcoding' | 64 | import { VideoTranscodingPayload } from '../../../lib/job-queue/handlers/video-transcoding' |
65 | import { Hooks } from '../../../lib/plugins/hooks' | 65 | import { Hooks } from '../../../lib/plugins/hooks' |
66 | import { MVideoDetails, MVideoFullLight } from '@server/typings/models' | ||
66 | 67 | ||
67 | const auditLogger = auditLoggerFactory('videos') | 68 | const auditLogger = auditLoggerFactory('videos') |
68 | const videosRouter = express.Router() | 69 | const videosRouter = express.Router() |
@@ -185,7 +186,7 @@ async function addVideo (req: express.Request, res: express.Response) { | |||
185 | licence: videoInfo.licence, | 186 | licence: videoInfo.licence, |
186 | language: videoInfo.language, | 187 | language: videoInfo.language, |
187 | commentsEnabled: videoInfo.commentsEnabled || false, | 188 | commentsEnabled: videoInfo.commentsEnabled || false, |
188 | downloadEnabled: videoInfo.downloadEnabled || true, | 189 | downloadEnabled: videoInfo.downloadEnabled !== false, // If the value is not "false", the default is "true" |
189 | waitTranscoding: videoInfo.waitTranscoding || false, | 190 | waitTranscoding: videoInfo.waitTranscoding || false, |
190 | state: CONFIG.TRANSCODING.ENABLED ? VideoState.TO_TRANSCODE : VideoState.PUBLISHED, | 191 | state: CONFIG.TRANSCODING.ENABLED ? VideoState.TO_TRANSCODE : VideoState.PUBLISHED, |
191 | nsfw: videoInfo.nsfw || false, | 192 | nsfw: videoInfo.nsfw || false, |
@@ -197,7 +198,7 @@ async function addVideo (req: express.Request, res: express.Response) { | |||
197 | originallyPublishedAt: videoInfo.originallyPublishedAt | 198 | originallyPublishedAt: videoInfo.originallyPublishedAt |
198 | } | 199 | } |
199 | 200 | ||
200 | const video = new VideoModel(videoData) | 201 | const video = new VideoModel(videoData) as MVideoDetails |
201 | video.url = getVideoActivityPubUrl(video) // We use the UUID, so set the URL after building the object | 202 | video.url = getVideoActivityPubUrl(video) // We use the UUID, so set the URL after building the object |
202 | 203 | ||
203 | const videoFile = new VideoFileModel({ | 204 | const videoFile = new VideoFileModel({ |
@@ -238,7 +239,7 @@ async function addVideo (req: express.Request, res: express.Response) { | |||
238 | const { videoCreated } = await sequelizeTypescript.transaction(async t => { | 239 | const { videoCreated } = await sequelizeTypescript.transaction(async t => { |
239 | const sequelizeOptions = { transaction: t } | 240 | const sequelizeOptions = { transaction: t } |
240 | 241 | ||
241 | const videoCreated = await video.save(sequelizeOptions) | 242 | const videoCreated = await video.save(sequelizeOptions) as MVideoFullLight |
242 | 243 | ||
243 | await videoCreated.addAndSaveThumbnail(thumbnailModel, t) | 244 | await videoCreated.addAndSaveThumbnail(thumbnailModel, t) |
244 | await videoCreated.addAndSaveThumbnail(previewModel, t) | 245 | await videoCreated.addAndSaveThumbnail(previewModel, t) |
@@ -318,7 +319,7 @@ async function addVideo (req: express.Request, res: express.Response) { | |||
318 | } | 319 | } |
319 | 320 | ||
320 | async function updateVideo (req: express.Request, res: express.Response) { | 321 | async function updateVideo (req: express.Request, res: express.Response) { |
321 | const videoInstance = res.locals.video | 322 | const videoInstance = res.locals.videoAll |
322 | const videoFieldsSave = videoInstance.toJSON() | 323 | const videoFieldsSave = videoInstance.toJSON() |
323 | const oldVideoAuditView = new VideoAuditView(videoInstance.toFormattedDetailsJSON()) | 324 | const oldVideoAuditView = new VideoAuditView(videoInstance.toFormattedDetailsJSON()) |
324 | const videoInfoToUpdate: VideoUpdate = req.body | 325 | const videoInfoToUpdate: VideoUpdate = req.body |
@@ -371,7 +372,7 @@ async function updateVideo (req: express.Request, res: express.Response) { | |||
371 | } | 372 | } |
372 | } | 373 | } |
373 | 374 | ||
374 | const videoInstanceUpdated = await videoInstance.save(sequelizeOptions) | 375 | const videoInstanceUpdated = await videoInstance.save(sequelizeOptions) as MVideoFullLight |
375 | 376 | ||
376 | if (thumbnailModel) await videoInstanceUpdated.addAndSaveThumbnail(thumbnailModel, t) | 377 | if (thumbnailModel) await videoInstanceUpdated.addAndSaveThumbnail(thumbnailModel, t) |
377 | if (previewModel) await videoInstanceUpdated.addAndSaveThumbnail(previewModel, t) | 378 | if (previewModel) await videoInstanceUpdated.addAndSaveThumbnail(previewModel, t) |
@@ -447,7 +448,7 @@ async function getVideo (req: express.Request, res: express.Response) { | |||
447 | 448 | ||
448 | const video = await Hooks.wrapPromiseFun( | 449 | const video = await Hooks.wrapPromiseFun( |
449 | VideoModel.loadForGetAPI, | 450 | VideoModel.loadForGetAPI, |
450 | { id: res.locals.video.id, userId }, | 451 | { id: res.locals.onlyVideoWithRights.id, userId }, |
451 | 'filter:api.video.get.result' | 452 | 'filter:api.video.get.result' |
452 | ) | 453 | ) |
453 | 454 | ||
@@ -460,7 +461,7 @@ async function getVideo (req: express.Request, res: express.Response) { | |||
460 | } | 461 | } |
461 | 462 | ||
462 | async function viewVideo (req: express.Request, res: express.Response) { | 463 | async function viewVideo (req: express.Request, res: express.Response) { |
463 | const videoInstance = res.locals.video | 464 | const videoInstance = res.locals.videoAll |
464 | 465 | ||
465 | const ip = req.ip | 466 | const ip = req.ip |
466 | const exists = await Redis.Instance.doesVideoIPViewExist(ip, videoInstance.uuid) | 467 | const exists = await Redis.Instance.doesVideoIPViewExist(ip, videoInstance.uuid) |
@@ -483,7 +484,7 @@ async function viewVideo (req: express.Request, res: express.Response) { | |||
483 | } | 484 | } |
484 | 485 | ||
485 | async function getVideoDescription (req: express.Request, res: express.Response) { | 486 | async function getVideoDescription (req: express.Request, res: express.Response) { |
486 | const videoInstance = res.locals.video | 487 | const videoInstance = res.locals.videoAll |
487 | let description = '' | 488 | let description = '' |
488 | 489 | ||
489 | if (videoInstance.isOwned()) { | 490 | if (videoInstance.isOwned()) { |
@@ -522,7 +523,7 @@ async function listVideos (req: express.Request, res: express.Response) { | |||
522 | } | 523 | } |
523 | 524 | ||
524 | async function removeVideo (req: express.Request, res: express.Response) { | 525 | async function removeVideo (req: express.Request, res: express.Response) { |
525 | const videoInstance = res.locals.video | 526 | const videoInstance = res.locals.videoAll |
526 | 527 | ||
527 | await sequelizeTypescript.transaction(async t => { | 528 | await sequelizeTypescript.transaction(async t => { |
528 | await videoInstance.destroy({ transaction: t }) | 529 | await videoInstance.destroy({ transaction: t }) |
diff --git a/server/controllers/api/videos/ownership.ts b/server/controllers/api/videos/ownership.ts index 5272c1385..abb34082e 100644 --- a/server/controllers/api/videos/ownership.ts +++ b/server/controllers/api/videos/ownership.ts | |||
@@ -18,6 +18,7 @@ import { getFormattedObjects } from '../../../helpers/utils' | |||
18 | import { changeVideoChannelShare } from '../../../lib/activitypub' | 18 | import { changeVideoChannelShare } from '../../../lib/activitypub' |
19 | import { sendUpdateVideo } from '../../../lib/activitypub/send' | 19 | import { sendUpdateVideo } from '../../../lib/activitypub/send' |
20 | import { VideoModel } from '../../../models/video/video' | 20 | import { VideoModel } from '../../../models/video/video' |
21 | import { MVideoFullLight } from '@server/typings/models' | ||
21 | 22 | ||
22 | const ownershipVideoRouter = express.Router() | 23 | const ownershipVideoRouter = express.Router() |
23 | 24 | ||
@@ -56,7 +57,7 @@ export { | |||
56 | // --------------------------------------------------------------------------- | 57 | // --------------------------------------------------------------------------- |
57 | 58 | ||
58 | async function giveVideoOwnership (req: express.Request, res: express.Response) { | 59 | async function giveVideoOwnership (req: express.Request, res: express.Response) { |
59 | const videoInstance = res.locals.video | 60 | const videoInstance = res.locals.videoAll |
60 | const initiatorAccountId = res.locals.oauth.token.User.Account.id | 61 | const initiatorAccountId = res.locals.oauth.token.User.Account.id |
61 | const nextOwner = res.locals.nextOwner | 62 | const nextOwner = res.locals.nextOwner |
62 | 63 | ||
@@ -107,7 +108,7 @@ async function acceptOwnership (req: express.Request, res: express.Response) { | |||
107 | 108 | ||
108 | targetVideo.channelId = channel.id | 109 | targetVideo.channelId = channel.id |
109 | 110 | ||
110 | const targetVideoUpdated = await targetVideo.save({ transaction: t }) | 111 | const targetVideoUpdated = await targetVideo.save({ transaction: t }) as MVideoFullLight |
111 | targetVideoUpdated.VideoChannel = channel | 112 | targetVideoUpdated.VideoChannel = channel |
112 | 113 | ||
113 | if (targetVideoUpdated.privacy !== VideoPrivacy.PRIVATE && targetVideoUpdated.state === VideoState.PUBLISHED) { | 114 | if (targetVideoUpdated.privacy !== VideoPrivacy.PRIVATE && targetVideoUpdated.state === VideoState.PUBLISHED) { |
diff --git a/server/controllers/api/videos/rate.ts b/server/controllers/api/videos/rate.ts index b65babedf..3d2f3d728 100644 --- a/server/controllers/api/videos/rate.ts +++ b/server/controllers/api/videos/rate.ts | |||
@@ -27,7 +27,7 @@ export { | |||
27 | async function rateVideo (req: express.Request, res: express.Response) { | 27 | async function rateVideo (req: express.Request, res: express.Response) { |
28 | const body: UserVideoRateUpdate = req.body | 28 | const body: UserVideoRateUpdate = req.body |
29 | const rateType = body.rating | 29 | const rateType = body.rating |
30 | const videoInstance = res.locals.video | 30 | const videoInstance = res.locals.videoAll |
31 | const userAccount = res.locals.oauth.token.User.Account | 31 | const userAccount = res.locals.oauth.token.User.Account |
32 | 32 | ||
33 | await sequelizeTypescript.transaction(async t => { | 33 | await sequelizeTypescript.transaction(async t => { |
diff --git a/server/controllers/api/videos/watching.ts b/server/controllers/api/videos/watching.ts index dcd1f070d..036e16f3a 100644 --- a/server/controllers/api/videos/watching.ts +++ b/server/controllers/api/videos/watching.ts | |||
@@ -23,7 +23,7 @@ async function userWatchVideo (req: express.Request, res: express.Response) { | |||
23 | const user = res.locals.oauth.token.User | 23 | const user = res.locals.oauth.token.User |
24 | 24 | ||
25 | const body: UserWatchingVideo = req.body | 25 | const body: UserWatchingVideo = req.body |
26 | const { id: videoId } = res.locals.video as { id: number } | 26 | const { id: videoId } = res.locals.videoId |
27 | 27 | ||
28 | await UserVideoHistoryModel.upsert({ | 28 | await UserVideoHistoryModel.upsert({ |
29 | videoId, | 29 | videoId, |
diff --git a/server/controllers/feeds.ts b/server/controllers/feeds.ts index d3f581615..468f7a668 100644 --- a/server/controllers/feeds.ts +++ b/server/controllers/feeds.ts | |||
@@ -43,7 +43,7 @@ export { | |||
43 | async function generateVideoCommentsFeed (req: express.Request, res: express.Response) { | 43 | async function generateVideoCommentsFeed (req: express.Request, res: express.Response) { |
44 | const start = 0 | 44 | const start = 0 |
45 | 45 | ||
46 | const video = res.locals.video | 46 | const video = res.locals.videoAll |
47 | const videoId: number = video ? video.id : undefined | 47 | const videoId: number = video ? video.id : undefined |
48 | 48 | ||
49 | const comments = await VideoCommentModel.listForFeed(start, FEEDS.COUNT, videoId) | 49 | const comments = await VideoCommentModel.listForFeed(start, FEEDS.COUNT, videoId) |
diff --git a/server/controllers/services.ts b/server/controllers/services.ts index c1c53c3fc..ec057235f 100644 --- a/server/controllers/services.ts +++ b/server/controllers/services.ts | |||
@@ -23,7 +23,7 @@ export { | |||
23 | // --------------------------------------------------------------------------- | 23 | // --------------------------------------------------------------------------- |
24 | 24 | ||
25 | function generateOEmbed (req: express.Request, res: express.Response) { | 25 | function generateOEmbed (req: express.Request, res: express.Response) { |
26 | const video = res.locals.video | 26 | const video = res.locals.videoAll |
27 | const webserverUrl = WEBSERVER.URL | 27 | const webserverUrl = WEBSERVER.URL |
28 | const maxHeight = parseInt(req.query.maxheight, 10) | 28 | const maxHeight = parseInt(req.query.maxheight, 10) |
29 | const maxWidth = parseInt(req.query.maxwidth, 10) | 29 | const maxWidth = parseInt(req.query.maxwidth, 10) |
diff --git a/server/controllers/static.ts b/server/controllers/static.ts index 8979ef5f3..0f4772310 100644 --- a/server/controllers/static.ts +++ b/server/controllers/static.ts | |||
@@ -226,14 +226,14 @@ async function generateNodeinfo (req: express.Request, res: express.Response) { | |||
226 | return res.send(json).end() | 226 | return res.send(json).end() |
227 | } | 227 | } |
228 | 228 | ||
229 | async function downloadTorrent (req: express.Request, res: express.Response, next: express.NextFunction) { | 229 | async function downloadTorrent (req: express.Request, res: express.Response) { |
230 | const { video, videoFile } = getVideoAndFile(req, res) | 230 | const { video, videoFile } = getVideoAndFile(req, res) |
231 | if (!videoFile) return res.status(404).end() | 231 | if (!videoFile) return res.status(404).end() |
232 | 232 | ||
233 | return res.download(video.getTorrentFilePath(videoFile), `${video.name}-${videoFile.resolution}p.torrent`) | 233 | return res.download(video.getTorrentFilePath(videoFile), `${video.name}-${videoFile.resolution}p.torrent`) |
234 | } | 234 | } |
235 | 235 | ||
236 | async function downloadVideoFile (req: express.Request, res: express.Response, next: express.NextFunction) { | 236 | async function downloadVideoFile (req: express.Request, res: express.Response) { |
237 | const { video, videoFile } = getVideoAndFile(req, res) | 237 | const { video, videoFile } = getVideoAndFile(req, res) |
238 | if (!videoFile) return res.status(404).end() | 238 | if (!videoFile) return res.status(404).end() |
239 | 239 | ||
@@ -242,7 +242,7 @@ async function downloadVideoFile (req: express.Request, res: express.Response, n | |||
242 | 242 | ||
243 | function getVideoAndFile (req: express.Request, res: express.Response) { | 243 | function getVideoAndFile (req: express.Request, res: express.Response) { |
244 | const resolution = parseInt(req.params.resolution, 10) | 244 | const resolution = parseInt(req.params.resolution, 10) |
245 | const video = res.locals.video | 245 | const video = res.locals.videoAll |
246 | 246 | ||
247 | const videoFile = video.VideoFiles.find(f => f.resolution === resolution) | 247 | const videoFile = video.VideoFiles.find(f => f.resolution === resolution) |
248 | 248 | ||
diff --git a/server/controllers/webfinger.ts b/server/controllers/webfinger.ts index f2ba3c826..fc9575160 100644 --- a/server/controllers/webfinger.ts +++ b/server/controllers/webfinger.ts | |||
@@ -18,7 +18,7 @@ export { | |||
18 | // --------------------------------------------------------------------------- | 18 | // --------------------------------------------------------------------------- |
19 | 19 | ||
20 | function webfingerController (req: express.Request, res: express.Response) { | 20 | function webfingerController (req: express.Request, res: express.Response) { |
21 | const actor = res.locals.actor | 21 | const actor = res.locals.actorFull |
22 | 22 | ||
23 | const json = { | 23 | const json = { |
24 | subject: req.query.resource, | 24 | subject: req.query.resource, |
diff --git a/server/helpers/activitypub.ts b/server/helpers/activitypub.ts index 951a25669..97c809a0c 100644 --- a/server/helpers/activitypub.ts +++ b/server/helpers/activitypub.ts | |||
@@ -7,6 +7,7 @@ import { ActorModel } from '../models/activitypub/actor' | |||
7 | import { signJsonLDObject } from './peertube-crypto' | 7 | import { signJsonLDObject } from './peertube-crypto' |
8 | import { pageToStartAndCount } from './core-utils' | 8 | import { pageToStartAndCount } from './core-utils' |
9 | import { parse } from 'url' | 9 | import { parse } from 'url' |
10 | import { MActor } from '../typings/models' | ||
10 | 11 | ||
11 | function activityPubContextify <T> (data: T) { | 12 | function activityPubContextify <T> (data: T) { |
12 | return Object.assign(data, { | 13 | return Object.assign(data, { |
@@ -143,7 +144,7 @@ async function activityPubCollectionPagination (baseUrl: string, handler: Activi | |||
143 | 144 | ||
144 | } | 145 | } |
145 | 146 | ||
146 | function buildSignedActivity (byActor: ActorModel, data: Object) { | 147 | function buildSignedActivity (byActor: MActor, data: Object) { |
147 | const activity = activityPubContextify(data) | 148 | const activity = activityPubContextify(data) |
148 | 149 | ||
149 | return signJsonLDObject(byActor, activity) as Promise<Activity> | 150 | return signJsonLDObject(byActor, activity) as Promise<Activity> |
diff --git a/server/helpers/actor.ts b/server/helpers/actor.ts index 12a7ace9f..117548a60 100644 --- a/server/helpers/actor.ts +++ b/server/helpers/actor.ts | |||
@@ -1,10 +1,13 @@ | |||
1 | import { ActorModel } from '../models/activitypub/actor' | 1 | import { ActorModel } from '../models/activitypub/actor' |
2 | import * as Bluebird from 'bluebird' | ||
3 | import { MActorFull, MActorAccountChannelId } from '../typings/models' | ||
2 | 4 | ||
3 | type ActorFetchByUrlType = 'all' | 'actor-and-association-ids' | 5 | type ActorFetchByUrlType = 'all' | 'association-ids' |
4 | function fetchActorByUrl (url: string, fetchType: ActorFetchByUrlType) { | 6 | |
7 | function fetchActorByUrl (url: string, fetchType: ActorFetchByUrlType): Bluebird<MActorFull | MActorAccountChannelId> { | ||
5 | if (fetchType === 'all') return ActorModel.loadByUrlAndPopulateAccountAndChannel(url) | 8 | if (fetchType === 'all') return ActorModel.loadByUrlAndPopulateAccountAndChannel(url) |
6 | 9 | ||
7 | if (fetchType === 'actor-and-association-ids') return ActorModel.loadByUrl(url) | 10 | if (fetchType === 'association-ids') return ActorModel.loadByUrl(url) |
8 | } | 11 | } |
9 | 12 | ||
10 | export { | 13 | export { |
diff --git a/server/helpers/captions-utils.ts b/server/helpers/captions-utils.ts index 7174d4654..2830ae017 100644 --- a/server/helpers/captions-utils.ts +++ b/server/helpers/captions-utils.ts | |||
@@ -1,10 +1,10 @@ | |||
1 | import { join } from 'path' | 1 | import { join } from 'path' |
2 | import { CONFIG } from '../initializers/config' | 2 | import { CONFIG } from '../initializers/config' |
3 | import { VideoCaptionModel } from '../models/video/video-caption' | ||
4 | import * as srt2vtt from 'srt-to-vtt' | 3 | import * as srt2vtt from 'srt-to-vtt' |
5 | import { createReadStream, createWriteStream, remove, move } from 'fs-extra' | 4 | import { createReadStream, createWriteStream, move, remove } from 'fs-extra' |
5 | import { MVideoCaptionFormattable } from '@server/typings/models' | ||
6 | 6 | ||
7 | async function moveAndProcessCaptionFile (physicalFile: { filename: string, path: string }, videoCaption: VideoCaptionModel) { | 7 | async function moveAndProcessCaptionFile (physicalFile: { filename: string, path: string }, videoCaption: MVideoCaptionFormattable) { |
8 | const videoCaptionsDir = CONFIG.STORAGE.CAPTIONS_DIR | 8 | const videoCaptionsDir = CONFIG.STORAGE.CAPTIONS_DIR |
9 | const destination = join(videoCaptionsDir, videoCaption.getCaptionName()) | 9 | const destination = join(videoCaptionsDir, videoCaption.getCaptionName()) |
10 | 10 | ||
diff --git a/server/helpers/custom-jsonld-signature.ts b/server/helpers/custom-jsonld-signature.ts index a3bceb047..cb07fa3b2 100644 --- a/server/helpers/custom-jsonld-signature.ts +++ b/server/helpers/custom-jsonld-signature.ts | |||
@@ -1,6 +1,5 @@ | |||
1 | import * as AsyncLRU from 'async-lru' | 1 | import * as AsyncLRU from 'async-lru' |
2 | import * as jsonld from 'jsonld' | 2 | import * as jsonld from 'jsonld' |
3 | import * as jsig from 'jsonld-signatures' | ||
4 | import { logger } from './logger' | 3 | import { logger } from './logger' |
5 | 4 | ||
6 | const CACHE = { | 5 | const CACHE = { |
@@ -79,6 +78,4 @@ jsonld.documentLoader = (url, cb) => { | |||
79 | lru.get(url, cb) | 78 | lru.get(url, cb) |
80 | } | 79 | } |
81 | 80 | ||
82 | jsig.use('jsonld', jsonld) | 81 | export { jsonld } |
83 | |||
84 | export { jsig, jsonld } | ||
diff --git a/server/helpers/custom-validators/activitypub/actor.ts b/server/helpers/custom-validators/activitypub/actor.ts index deb331abb..55bc8cc96 100644 --- a/server/helpers/custom-validators/activitypub/actor.ts +++ b/server/helpers/custom-validators/activitypub/actor.ts | |||
@@ -27,7 +27,7 @@ function isActorPublicKeyValid (publicKey: string) { | |||
27 | validator.isLength(publicKey, CONSTRAINTS_FIELDS.ACTORS.PUBLIC_KEY) | 27 | validator.isLength(publicKey, CONSTRAINTS_FIELDS.ACTORS.PUBLIC_KEY) |
28 | } | 28 | } |
29 | 29 | ||
30 | const actorNameAlphabet = '[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\\-_.]' | 30 | const actorNameAlphabet = '[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\\-_.:]' |
31 | const actorNameRegExp = new RegExp(`^${actorNameAlphabet}+$`) | 31 | const actorNameRegExp = new RegExp(`^${actorNameAlphabet}+$`) |
32 | function isActorPreferredUsernameValid (preferredUsername: string) { | 32 | function isActorPreferredUsernameValid (preferredUsername: string) { |
33 | return exists(preferredUsername) && validator.matches(preferredUsername, actorNameRegExp) | 33 | return exists(preferredUsername) && validator.matches(preferredUsername, actorNameRegExp) |
@@ -46,19 +46,20 @@ function isActorObjectValid (actor: any) { | |||
46 | return exists(actor) && | 46 | return exists(actor) && |
47 | isActivityPubUrlValid(actor.id) && | 47 | isActivityPubUrlValid(actor.id) && |
48 | isActorTypeValid(actor.type) && | 48 | isActorTypeValid(actor.type) && |
49 | isActivityPubUrlValid(actor.following) && | ||
50 | isActivityPubUrlValid(actor.followers) && | ||
51 | isActivityPubUrlValid(actor.inbox) && | 49 | isActivityPubUrlValid(actor.inbox) && |
52 | isActivityPubUrlValid(actor.outbox) && | ||
53 | isActorPreferredUsernameValid(actor.preferredUsername) && | 50 | isActorPreferredUsernameValid(actor.preferredUsername) && |
54 | isActivityPubUrlValid(actor.url) && | 51 | isActivityPubUrlValid(actor.url) && |
55 | isActorPublicKeyObjectValid(actor.publicKey) && | 52 | isActorPublicKeyObjectValid(actor.publicKey) && |
56 | isActorEndpointsObjectValid(actor.endpoints) && | 53 | isActorEndpointsObjectValid(actor.endpoints) && |
57 | setValidAttributedTo(actor) && | ||
58 | 54 | ||
59 | // If this is not an account, it should be attributed to an account | 55 | (!actor.outbox || isActivityPubUrlValid(actor.outbox)) && |
56 | (!actor.following || isActivityPubUrlValid(actor.following)) && | ||
57 | (!actor.followers || isActivityPubUrlValid(actor.followers)) && | ||
58 | |||
59 | setValidAttributedTo(actor) && | ||
60 | // If this is a group (a channel), it should be attributed to an account | ||
60 | // In PeerTube we use this to attach a video channel to a specific account | 61 | // In PeerTube we use this to attach a video channel to a specific account |
61 | (actor.type === 'Person' || actor.attributedTo.length !== 0) | 62 | (actor.type !== 'Group' || actor.attributedTo.length !== 0) |
62 | } | 63 | } |
63 | 64 | ||
64 | function isActorFollowingCountValid (value: string) { | 65 | function isActorFollowingCountValid (value: string) { |
diff --git a/server/helpers/custom-validators/plugins.ts b/server/helpers/custom-validators/plugins.ts index 63af91a44..2e3175742 100644 --- a/server/helpers/custom-validators/plugins.ts +++ b/server/helpers/custom-validators/plugins.ts | |||
@@ -84,17 +84,65 @@ function isThemeNameValid (name: string) { | |||
84 | } | 84 | } |
85 | 85 | ||
86 | function isPackageJSONValid (packageJSON: PluginPackageJson, pluginType: PluginType) { | 86 | function isPackageJSONValid (packageJSON: PluginPackageJson, pluginType: PluginType) { |
87 | return isNpmPluginNameValid(packageJSON.name) && | 87 | let result = true |
88 | isPluginDescriptionValid(packageJSON.description) && | 88 | const badFields: string[] = [] |
89 | isPluginEngineValid(packageJSON.engine) && | 89 | |
90 | isPluginHomepage(packageJSON.homepage) && | 90 | if (!isNpmPluginNameValid(packageJSON.name)) { |
91 | exists(packageJSON.author) && | 91 | result = false |
92 | isPluginBugs(packageJSON.bugs) && | 92 | badFields.push('name') |
93 | (pluginType === PluginType.THEME || isSafePath(packageJSON.library)) && | 93 | } |
94 | areStaticDirectoriesValid(packageJSON.staticDirs) && | 94 | |
95 | areCSSPathsValid(packageJSON.css) && | 95 | if (!isPluginDescriptionValid(packageJSON.description)) { |
96 | areClientScriptsValid(packageJSON.clientScripts) && | 96 | result = false |
97 | areTranslationPathsValid(packageJSON.translations) | 97 | badFields.push('description') |
98 | } | ||
99 | |||
100 | if (!isPluginEngineValid(packageJSON.engine)) { | ||
101 | result = false | ||
102 | badFields.push('engine') | ||
103 | } | ||
104 | |||
105 | if (!isPluginHomepage(packageJSON.homepage)) { | ||
106 | result = false | ||
107 | badFields.push('homepage') | ||
108 | } | ||
109 | |||
110 | if (!exists(packageJSON.author)) { | ||
111 | result = false | ||
112 | badFields.push('author') | ||
113 | } | ||
114 | |||
115 | if (!isPluginBugs(packageJSON.bugs)) { | ||
116 | result = false | ||
117 | badFields.push('bugs') | ||
118 | } | ||
119 | |||
120 | if (pluginType === PluginType.PLUGIN && !isSafePath(packageJSON.library)) { | ||
121 | result = false | ||
122 | badFields.push('library') | ||
123 | } | ||
124 | |||
125 | if (!areStaticDirectoriesValid(packageJSON.staticDirs)) { | ||
126 | result = false | ||
127 | badFields.push('staticDirs') | ||
128 | } | ||
129 | |||
130 | if (!areCSSPathsValid(packageJSON.css)) { | ||
131 | result = false | ||
132 | badFields.push('css') | ||
133 | } | ||
134 | |||
135 | if (!areClientScriptsValid(packageJSON.clientScripts)) { | ||
136 | result = false | ||
137 | badFields.push('clientScripts') | ||
138 | } | ||
139 | |||
140 | if (!areTranslationPathsValid(packageJSON.translations)) { | ||
141 | result = false | ||
142 | badFields.push('translations') | ||
143 | } | ||
144 | |||
145 | return { result, badFields } | ||
98 | } | 146 | } |
99 | 147 | ||
100 | function isLibraryCodeValid (library: any) { | 148 | function isLibraryCodeValid (library: any) { |
diff --git a/server/helpers/custom-validators/users.ts b/server/helpers/custom-validators/users.ts index c56ae14ef..68e84d9eb 100644 --- a/server/helpers/custom-validators/users.ts +++ b/server/helpers/custom-validators/users.ts | |||
@@ -65,6 +65,14 @@ function isUserBlockedValid (value: any) { | |||
65 | return isBooleanValid(value) | 65 | return isBooleanValid(value) |
66 | } | 66 | } |
67 | 67 | ||
68 | function isNoInstanceConfigWarningModal (value: any) { | ||
69 | return isBooleanValid(value) | ||
70 | } | ||
71 | |||
72 | function isNoWelcomeModal (value: any) { | ||
73 | return isBooleanValid(value) | ||
74 | } | ||
75 | |||
68 | function isUserBlockedReasonValid (value: any) { | 76 | function isUserBlockedReasonValid (value: any) { |
69 | return value === null || (exists(value) && validator.isLength(value, CONSTRAINTS_FIELDS.USERS.BLOCKED_REASON)) | 77 | return value === null || (exists(value) && validator.isLength(value, CONSTRAINTS_FIELDS.USERS.BLOCKED_REASON)) |
70 | } | 78 | } |
@@ -100,5 +108,7 @@ export { | |||
100 | isUserAutoPlayVideoValid, | 108 | isUserAutoPlayVideoValid, |
101 | isUserDisplayNameValid, | 109 | isUserDisplayNameValid, |
102 | isUserDescriptionValid, | 110 | isUserDescriptionValid, |
111 | isNoInstanceConfigWarningModal, | ||
112 | isNoWelcomeModal, | ||
103 | isAvatarFile | 113 | isAvatarFile |
104 | } | 114 | } |
diff --git a/server/helpers/custom-validators/video-ownership.ts b/server/helpers/custom-validators/video-ownership.ts index a7771e07b..9570b2799 100644 --- a/server/helpers/custom-validators/video-ownership.ts +++ b/server/helpers/custom-validators/video-ownership.ts | |||
@@ -1,10 +1,10 @@ | |||
1 | import { Response } from 'express' | 1 | import { Response } from 'express' |
2 | import * as validator from 'validator' | ||
3 | import { VideoChangeOwnershipModel } from '../../models/video/video-change-ownership' | 2 | import { VideoChangeOwnershipModel } from '../../models/video/video-change-ownership' |
4 | import { UserModel } from '../../models/account/user' | 3 | import { MVideoChangeOwnershipFull } from '@server/typings/models/video/video-change-ownership' |
4 | import { MUserId } from '@server/typings/models' | ||
5 | 5 | ||
6 | export async function doesChangeVideoOwnershipExist (id: string, res: Response): Promise<boolean> { | 6 | export async function doesChangeVideoOwnershipExist (id: number, res: Response) { |
7 | const videoChangeOwnership = await loadVideoChangeOwnership(id) | 7 | const videoChangeOwnership = await VideoChangeOwnershipModel.load(id) |
8 | 8 | ||
9 | if (!videoChangeOwnership) { | 9 | if (!videoChangeOwnership) { |
10 | res.status(404) | 10 | res.status(404) |
@@ -18,19 +18,7 @@ export async function doesChangeVideoOwnershipExist (id: string, res: Response): | |||
18 | return true | 18 | return true |
19 | } | 19 | } |
20 | 20 | ||
21 | async function loadVideoChangeOwnership (id: string): Promise<VideoChangeOwnershipModel | undefined> { | 21 | export function checkUserCanTerminateOwnershipChange (user: MUserId, videoChangeOwnership: MVideoChangeOwnershipFull, res: Response) { |
22 | if (validator.isInt(id)) { | ||
23 | return VideoChangeOwnershipModel.load(parseInt(id, 10)) | ||
24 | } | ||
25 | |||
26 | return undefined | ||
27 | } | ||
28 | |||
29 | export function checkUserCanTerminateOwnershipChange ( | ||
30 | user: UserModel, | ||
31 | videoChangeOwnership: VideoChangeOwnershipModel, | ||
32 | res: Response | ||
33 | ): boolean { | ||
34 | if (videoChangeOwnership.NextOwner.userId === user.id) { | 22 | if (videoChangeOwnership.NextOwner.userId === user.id) { |
35 | return true | 23 | return true |
36 | } | 24 | } |
diff --git a/server/helpers/middlewares/accounts.ts b/server/helpers/middlewares/accounts.ts index 791022b97..f5aa0bada 100644 --- a/server/helpers/middlewares/accounts.ts +++ b/server/helpers/middlewares/accounts.ts | |||
@@ -1,6 +1,7 @@ | |||
1 | import { Response } from 'express' | 1 | import { Response } from 'express' |
2 | import { AccountModel } from '../../models/account/account' | 2 | import { AccountModel } from '../../models/account/account' |
3 | import * as Bluebird from 'bluebird' | 3 | import * as Bluebird from 'bluebird' |
4 | import { MAccountDefault } from '../../typings/models' | ||
4 | 5 | ||
5 | function doesAccountIdExist (id: number, res: Response, sendNotFound = true) { | 6 | function doesAccountIdExist (id: number, res: Response, sendNotFound = true) { |
6 | const promise = AccountModel.load(id) | 7 | const promise = AccountModel.load(id) |
@@ -15,10 +16,12 @@ function doesLocalAccountNameExist (name: string, res: Response, sendNotFound = | |||
15 | } | 16 | } |
16 | 17 | ||
17 | function doesAccountNameWithHostExist (nameWithDomain: string, res: Response, sendNotFound = true) { | 18 | function doesAccountNameWithHostExist (nameWithDomain: string, res: Response, sendNotFound = true) { |
18 | return doesAccountExist(AccountModel.loadByNameWithHost(nameWithDomain), res, sendNotFound) | 19 | const promise = AccountModel.loadByNameWithHost(nameWithDomain) |
20 | |||
21 | return doesAccountExist(promise, res, sendNotFound) | ||
19 | } | 22 | } |
20 | 23 | ||
21 | async function doesAccountExist (p: Bluebird<AccountModel>, res: Response, sendNotFound: boolean) { | 24 | async function doesAccountExist (p: Bluebird<MAccountDefault>, res: Response, sendNotFound: boolean) { |
22 | const account = await p | 25 | const account = await p |
23 | 26 | ||
24 | if (!account) { | 27 | if (!account) { |
diff --git a/server/helpers/middlewares/video-abuses.ts b/server/helpers/middlewares/video-abuses.ts index b23f1f021..1b573ca37 100644 --- a/server/helpers/middlewares/video-abuses.ts +++ b/server/helpers/middlewares/video-abuses.ts | |||
@@ -1,41 +1,23 @@ | |||
1 | import * as express from 'express' | 1 | import { Response } from 'express' |
2 | import { VideoChannelModel } from '../../models/video/video-channel' | 2 | import { VideoAbuseModel } from '../../models/video/video-abuse' |
3 | 3 | ||
4 | async function doesLocalVideoChannelNameExist (name: string, res: express.Response) { | 4 | async function doesVideoAbuseExist (abuseId: number, videoId: number, res: Response) { |
5 | const videoChannel = await VideoChannelModel.loadLocalByNameAndPopulateAccount(name) | 5 | const videoAbuse = await VideoAbuseModel.loadByIdAndVideoId(abuseId, videoId) |
6 | 6 | ||
7 | return processVideoChannelExist(videoChannel, res) | 7 | if (videoAbuse === null) { |
8 | } | ||
9 | |||
10 | async function doesVideoChannelIdExist (id: number, res: express.Response) { | ||
11 | const videoChannel = await VideoChannelModel.loadAndPopulateAccount(+id) | ||
12 | |||
13 | return processVideoChannelExist(videoChannel, res) | ||
14 | } | ||
15 | |||
16 | async function doesVideoChannelNameWithHostExist (nameWithDomain: string, res: express.Response) { | ||
17 | const videoChannel = await VideoChannelModel.loadByNameWithHostAndPopulateAccount(nameWithDomain) | ||
18 | |||
19 | return processVideoChannelExist(videoChannel, res) | ||
20 | } | ||
21 | |||
22 | // --------------------------------------------------------------------------- | ||
23 | |||
24 | export { | ||
25 | doesLocalVideoChannelNameExist, | ||
26 | doesVideoChannelIdExist, | ||
27 | doesVideoChannelNameWithHostExist | ||
28 | } | ||
29 | |||
30 | function processVideoChannelExist (videoChannel: VideoChannelModel, res: express.Response) { | ||
31 | if (!videoChannel) { | ||
32 | res.status(404) | 8 | res.status(404) |
33 | .json({ error: 'Video channel not found' }) | 9 | .json({ error: 'Video abuse not found' }) |
34 | .end() | 10 | .end() |
35 | 11 | ||
36 | return false | 12 | return false |
37 | } | 13 | } |
38 | 14 | ||
39 | res.locals.videoChannel = videoChannel | 15 | res.locals.videoAbuse = videoAbuse |
40 | return true | 16 | return true |
41 | } | 17 | } |
18 | |||
19 | // --------------------------------------------------------------------------- | ||
20 | |||
21 | export { | ||
22 | doesVideoAbuseExist | ||
23 | } | ||
diff --git a/server/helpers/middlewares/video-captions.ts b/server/helpers/middlewares/video-captions.ts index dc3d0144b..1b2513b60 100644 --- a/server/helpers/middlewares/video-captions.ts +++ b/server/helpers/middlewares/video-captions.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | import { VideoModel } from '../../models/video/video' | ||
2 | import { Response } from 'express' | 1 | import { Response } from 'express' |
3 | import { VideoCaptionModel } from '../../models/video/video-caption' | 2 | import { VideoCaptionModel } from '../../models/video/video-caption' |
3 | import { MVideoId } from '@server/typings/models' | ||
4 | 4 | ||
5 | async function doesVideoCaptionExist (video: VideoModel, language: string, res: Response) { | 5 | async function doesVideoCaptionExist (video: MVideoId, language: string, res: Response) { |
6 | const videoCaption = await VideoCaptionModel.loadByVideoIdAndLanguage(video.id, language) | 6 | const videoCaption = await VideoCaptionModel.loadByVideoIdAndLanguage(video.id, language) |
7 | 7 | ||
8 | if (!videoCaption) { | 8 | if (!videoCaption) { |
diff --git a/server/helpers/middlewares/video-channels.ts b/server/helpers/middlewares/video-channels.ts index 1b573ca37..1595ecd94 100644 --- a/server/helpers/middlewares/video-channels.ts +++ b/server/helpers/middlewares/video-channels.ts | |||
@@ -1,23 +1,42 @@ | |||
1 | import { Response } from 'express' | 1 | import * as express from 'express' |
2 | import { VideoAbuseModel } from '../../models/video/video-abuse' | 2 | import { VideoChannelModel } from '../../models/video/video-channel' |
3 | import { MChannelAccountDefault } from '@server/typings/models' | ||
3 | 4 | ||
4 | async function doesVideoAbuseExist (abuseId: number, videoId: number, res: Response) { | 5 | async function doesLocalVideoChannelNameExist (name: string, res: express.Response) { |
5 | const videoAbuse = await VideoAbuseModel.loadByIdAndVideoId(abuseId, videoId) | 6 | const videoChannel = await VideoChannelModel.loadLocalByNameAndPopulateAccount(name) |
6 | 7 | ||
7 | if (videoAbuse === null) { | 8 | return processVideoChannelExist(videoChannel, res) |
8 | res.status(404) | 9 | } |
9 | .json({ error: 'Video abuse not found' }) | ||
10 | .end() | ||
11 | 10 | ||
12 | return false | 11 | async function doesVideoChannelIdExist (id: number, res: express.Response) { |
13 | } | 12 | const videoChannel = await VideoChannelModel.loadAndPopulateAccount(+id) |
14 | 13 | ||
15 | res.locals.videoAbuse = videoAbuse | 14 | return processVideoChannelExist(videoChannel, res) |
16 | return true | 15 | } |
16 | |||
17 | async function doesVideoChannelNameWithHostExist (nameWithDomain: string, res: express.Response) { | ||
18 | const videoChannel = await VideoChannelModel.loadByNameWithHostAndPopulateAccount(nameWithDomain) | ||
19 | |||
20 | return processVideoChannelExist(videoChannel, res) | ||
17 | } | 21 | } |
18 | 22 | ||
19 | // --------------------------------------------------------------------------- | 23 | // --------------------------------------------------------------------------- |
20 | 24 | ||
21 | export { | 25 | export { |
22 | doesVideoAbuseExist | 26 | doesLocalVideoChannelNameExist, |
27 | doesVideoChannelIdExist, | ||
28 | doesVideoChannelNameWithHostExist | ||
29 | } | ||
30 | |||
31 | function processVideoChannelExist (videoChannel: MChannelAccountDefault, res: express.Response) { | ||
32 | if (!videoChannel) { | ||
33 | res.status(404) | ||
34 | .json({ error: 'Video channel not found' }) | ||
35 | .end() | ||
36 | |||
37 | return false | ||
38 | } | ||
39 | |||
40 | res.locals.videoChannel = videoChannel | ||
41 | return true | ||
23 | } | 42 | } |
diff --git a/server/helpers/middlewares/video-playlists.ts b/server/helpers/middlewares/video-playlists.ts index 735bf362f..8e7484483 100644 --- a/server/helpers/middlewares/video-playlists.ts +++ b/server/helpers/middlewares/video-playlists.ts | |||
@@ -1,11 +1,31 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { VideoPlaylistModel } from '../../models/video/video-playlist' | 2 | import { VideoPlaylistModel } from '../../models/video/video-playlist' |
3 | import { MVideoPlaylist } from '../../typings/models/video/video-playlist' | ||
3 | 4 | ||
4 | async function doesVideoPlaylistExist (id: number | string, res: express.Response, fetchType: 'summary' | 'all' = 'summary') { | 5 | export type VideoPlaylistFetchType = 'summary' | 'all' |
5 | const videoPlaylist = fetchType === 'summary' | 6 | async function doesVideoPlaylistExist (id: number | string, res: express.Response, fetchType: VideoPlaylistFetchType = 'summary') { |
6 | ? await VideoPlaylistModel.loadWithAccountAndChannelSummary(id, undefined) | 7 | if (fetchType === 'summary') { |
7 | : await VideoPlaylistModel.loadWithAccountAndChannel(id, undefined) | 8 | const videoPlaylist = await VideoPlaylistModel.loadWithAccountAndChannelSummary(id, undefined) |
9 | res.locals.videoPlaylistSummary = videoPlaylist | ||
8 | 10 | ||
11 | return handleVideoPlaylist(videoPlaylist, res) | ||
12 | } | ||
13 | |||
14 | const videoPlaylist = await VideoPlaylistModel.loadWithAccountAndChannel(id, undefined) | ||
15 | res.locals.videoPlaylistFull = videoPlaylist | ||
16 | |||
17 | return handleVideoPlaylist(videoPlaylist, res) | ||
18 | } | ||
19 | |||
20 | // --------------------------------------------------------------------------- | ||
21 | |||
22 | export { | ||
23 | doesVideoPlaylistExist | ||
24 | } | ||
25 | |||
26 | // --------------------------------------------------------------------------- | ||
27 | |||
28 | function handleVideoPlaylist (videoPlaylist: MVideoPlaylist, res: express.Response) { | ||
9 | if (!videoPlaylist) { | 29 | if (!videoPlaylist) { |
10 | res.status(404) | 30 | res.status(404) |
11 | .json({ error: 'Video playlist not found' }) | 31 | .json({ error: 'Video playlist not found' }) |
@@ -14,12 +34,5 @@ async function doesVideoPlaylistExist (id: number | string, res: express.Respons | |||
14 | return false | 34 | return false |
15 | } | 35 | } |
16 | 36 | ||
17 | res.locals.videoPlaylist = videoPlaylist | ||
18 | return true | 37 | return true |
19 | } | 38 | } |
20 | |||
21 | // --------------------------------------------------------------------------- | ||
22 | |||
23 | export { | ||
24 | doesVideoPlaylistExist | ||
25 | } | ||
diff --git a/server/helpers/middlewares/videos.ts b/server/helpers/middlewares/videos.ts index ceb1058ec..74f529804 100644 --- a/server/helpers/middlewares/videos.ts +++ b/server/helpers/middlewares/videos.ts | |||
@@ -1,9 +1,8 @@ | |||
1 | import { Response } from 'express' | 1 | import { Response } from 'express' |
2 | import { fetchVideo, VideoFetchType } from '../video' | 2 | import { fetchVideo, VideoFetchType } from '../video' |
3 | import { UserModel } from '../../models/account/user' | ||
4 | import { UserRight } from '../../../shared/models/users' | 3 | import { UserRight } from '../../../shared/models/users' |
5 | import { VideoChannelModel } from '../../models/video/video-channel' | 4 | import { VideoChannelModel } from '../../models/video/video-channel' |
6 | import { VideoModel } from '../../models/video/video' | 5 | import { MUser, MUserAccountId, MVideoAccountLight, MVideoFullLight, MVideoThumbnail, MVideoWithRights } from '@server/typings/models' |
7 | 6 | ||
8 | async function doesVideoExist (id: number | string, res: Response, fetchType: VideoFetchType = 'all') { | 7 | async function doesVideoExist (id: number | string, res: Response, fetchType: VideoFetchType = 'all') { |
9 | const userId = res.locals.oauth ? res.locals.oauth.token.User.id : undefined | 8 | const userId = res.locals.oauth ? res.locals.oauth.token.User.id : undefined |
@@ -18,11 +17,28 @@ async function doesVideoExist (id: number | string, res: Response, fetchType: Vi | |||
18 | return false | 17 | return false |
19 | } | 18 | } |
20 | 19 | ||
21 | if (fetchType !== 'none') res.locals.video = video | 20 | switch (fetchType) { |
21 | case 'all': | ||
22 | res.locals.videoAll = video as MVideoFullLight | ||
23 | break | ||
24 | |||
25 | case 'id': | ||
26 | res.locals.videoId = video | ||
27 | break | ||
28 | |||
29 | case 'only-video': | ||
30 | res.locals.onlyVideo = video as MVideoThumbnail | ||
31 | break | ||
32 | |||
33 | case 'only-video-with-rights': | ||
34 | res.locals.onlyVideoWithRights = video as MVideoWithRights | ||
35 | break | ||
36 | } | ||
37 | |||
22 | return true | 38 | return true |
23 | } | 39 | } |
24 | 40 | ||
25 | async function doesVideoChannelOfAccountExist (channelId: number, user: UserModel, res: Response) { | 41 | async function doesVideoChannelOfAccountExist (channelId: number, user: MUserAccountId, res: Response) { |
26 | if (user.hasRight(UserRight.UPDATE_ANY_VIDEO) === true) { | 42 | if (user.hasRight(UserRight.UPDATE_ANY_VIDEO) === true) { |
27 | const videoChannel = await VideoChannelModel.loadAndPopulateAccount(channelId) | 43 | const videoChannel = await VideoChannelModel.loadAndPopulateAccount(channelId) |
28 | if (videoChannel === null) { | 44 | if (videoChannel === null) { |
@@ -50,7 +66,7 @@ async function doesVideoChannelOfAccountExist (channelId: number, user: UserMode | |||
50 | return true | 66 | return true |
51 | } | 67 | } |
52 | 68 | ||
53 | function checkUserCanManageVideo (user: UserModel, video: VideoModel, right: UserRight, res: Response) { | 69 | function checkUserCanManageVideo (user: MUser, video: MVideoAccountLight, right: UserRight, res: Response) { |
54 | // Retrieve the user who did the request | 70 | // Retrieve the user who did the request |
55 | if (video.isOwned() === false) { | 71 | if (video.isOwned() === false) { |
56 | res.status(403) | 72 | res.status(403) |
diff --git a/server/helpers/peertube-crypto.ts b/server/helpers/peertube-crypto.ts index 1424949d0..9eb782302 100644 --- a/server/helpers/peertube-crypto.ts +++ b/server/helpers/peertube-crypto.ts | |||
@@ -1,13 +1,13 @@ | |||
1 | import { Request } from 'express' | 1 | import { Request } from 'express' |
2 | import { BCRYPT_SALT_SIZE, HTTP_SIGNATURE, PRIVATE_RSA_KEY_SIZE } from '../initializers/constants' | 2 | import { BCRYPT_SALT_SIZE, HTTP_SIGNATURE, PRIVATE_RSA_KEY_SIZE } from '../initializers/constants' |
3 | import { ActorModel } from '../models/activitypub/actor' | ||
4 | import { createPrivateKey, getPublicKey, promisify1, promisify2, sha256 } from './core-utils' | 3 | import { createPrivateKey, getPublicKey, promisify1, promisify2, sha256 } from './core-utils' |
5 | import { jsig, jsonld } from './custom-jsonld-signature' | 4 | import { jsonld } from './custom-jsonld-signature' |
6 | import { logger } from './logger' | 5 | import { logger } from './logger' |
7 | import { cloneDeep } from 'lodash' | 6 | import { cloneDeep } from 'lodash' |
8 | import { createVerify } from 'crypto' | 7 | import { createSign, createVerify } from 'crypto' |
9 | import { buildDigest } from '../lib/job-queue/handlers/utils/activitypub-http-utils' | 8 | import { buildDigest } from '../lib/job-queue/handlers/utils/activitypub-http-utils' |
10 | import * as bcrypt from 'bcrypt' | 9 | import * as bcrypt from 'bcrypt' |
10 | import { MActor } from '../typings/models' | ||
11 | 11 | ||
12 | const bcryptComparePromise = promisify2<any, string, boolean>(bcrypt.compare) | 12 | const bcryptComparePromise = promisify2<any, string, boolean>(bcrypt.compare) |
13 | const bcryptGenSaltPromise = promisify1<number, string>(bcrypt.genSalt) | 13 | const bcryptGenSaltPromise = promisify1<number, string>(bcrypt.genSalt) |
@@ -46,7 +46,7 @@ function isHTTPSignatureDigestValid (rawBody: Buffer, req: Request): boolean { | |||
46 | return true | 46 | return true |
47 | } | 47 | } |
48 | 48 | ||
49 | function isHTTPSignatureVerified (httpSignatureParsed: any, actor: ActorModel): boolean { | 49 | function isHTTPSignatureVerified (httpSignatureParsed: any, actor: MActor): boolean { |
50 | return httpSignature.verifySignature(httpSignatureParsed, actor.publicKey) === true | 50 | return httpSignature.verifySignature(httpSignatureParsed, actor.publicKey) === true |
51 | } | 51 | } |
52 | 52 | ||
@@ -56,70 +56,21 @@ function parseHTTPSignature (req: Request, clockSkew?: number) { | |||
56 | 56 | ||
57 | // JSONLD | 57 | // JSONLD |
58 | 58 | ||
59 | async function isJsonLDSignatureVerified (fromActor: ActorModel, signedDocument: any): Promise<boolean> { | 59 | function isJsonLDSignatureVerified (fromActor: MActor, signedDocument: any): Promise<boolean> { |
60 | if (signedDocument.signature.type === 'RsaSignature2017') { | 60 | if (signedDocument.signature.type === 'RsaSignature2017') { |
61 | // Mastodon algorithm | 61 | return isJsonLDRSA2017Verified(fromActor, signedDocument) |
62 | const res = await isJsonLDRSA2017Verified(fromActor, signedDocument) | ||
63 | // Success? If no, try with our library | ||
64 | if (res === true) return true | ||
65 | } | 62 | } |
66 | 63 | ||
67 | const publicKeyObject = { | 64 | logger.warn('Unknown JSON LD signature %s.', signedDocument.signature.type, signedDocument) |
68 | '@context': jsig.SECURITY_CONTEXT_URL, | ||
69 | id: fromActor.url, | ||
70 | type: 'CryptographicKey', | ||
71 | owner: fromActor.url, | ||
72 | publicKeyPem: fromActor.publicKey | ||
73 | } | ||
74 | |||
75 | const publicKeyOwnerObject = { | ||
76 | '@context': jsig.SECURITY_CONTEXT_URL, | ||
77 | id: fromActor.url, | ||
78 | publicKey: [ publicKeyObject ] | ||
79 | } | ||
80 | 65 | ||
81 | const options = { | 66 | return Promise.resolve(false) |
82 | publicKey: publicKeyObject, | ||
83 | publicKeyOwner: publicKeyOwnerObject | ||
84 | } | ||
85 | |||
86 | return jsig.promises | ||
87 | .verify(signedDocument, options) | ||
88 | .then((result: { verified: boolean }) => result.verified) | ||
89 | .catch(err => { | ||
90 | logger.error('Cannot check signature.', { err }) | ||
91 | return false | ||
92 | }) | ||
93 | } | 67 | } |
94 | 68 | ||
95 | // Backward compatibility with "other" implementations | 69 | // Backward compatibility with "other" implementations |
96 | async function isJsonLDRSA2017Verified (fromActor: ActorModel, signedDocument: any) { | 70 | async function isJsonLDRSA2017Verified (fromActor: MActor, signedDocument: any) { |
97 | function hash (obj: any): Promise<any> { | ||
98 | return jsonld.promises | ||
99 | .normalize(obj, { | ||
100 | algorithm: 'URDNA2015', | ||
101 | format: 'application/n-quads' | ||
102 | }) | ||
103 | .then(res => sha256(res)) | ||
104 | } | ||
105 | |||
106 | const signatureCopy = cloneDeep(signedDocument.signature) | ||
107 | Object.assign(signatureCopy, { | ||
108 | '@context': [ | ||
109 | 'https://w3id.org/security/v1', | ||
110 | { RsaSignature2017: 'https://w3id.org/security#RsaSignature2017' } | ||
111 | ] | ||
112 | }) | ||
113 | delete signatureCopy.type | ||
114 | delete signatureCopy.id | ||
115 | delete signatureCopy.signatureValue | ||
116 | |||
117 | const docWithoutSignature = cloneDeep(signedDocument) | ||
118 | delete docWithoutSignature.signature | ||
119 | |||
120 | const [ documentHash, optionsHash ] = await Promise.all([ | 71 | const [ documentHash, optionsHash ] = await Promise.all([ |
121 | hash(docWithoutSignature), | 72 | createDocWithoutSignatureHash(signedDocument), |
122 | hash(signatureCopy) | 73 | createSignatureHash(signedDocument.signature) |
123 | ]) | 74 | ]) |
124 | 75 | ||
125 | const toVerify = optionsHash + documentHash | 76 | const toVerify = optionsHash + documentHash |
@@ -130,14 +81,27 @@ async function isJsonLDRSA2017Verified (fromActor: ActorModel, signedDocument: a | |||
130 | return verify.verify(fromActor.publicKey, signedDocument.signature.signatureValue, 'base64') | 81 | return verify.verify(fromActor.publicKey, signedDocument.signature.signatureValue, 'base64') |
131 | } | 82 | } |
132 | 83 | ||
133 | function signJsonLDObject (byActor: ActorModel, data: any) { | 84 | async function signJsonLDObject (byActor: MActor, data: any) { |
134 | const options = { | 85 | const signature = { |
135 | privateKeyPem: byActor.privateKey, | 86 | type: 'RsaSignature2017', |
136 | creator: byActor.url, | 87 | creator: byActor.url, |
137 | algorithm: 'RsaSignature2017' | 88 | created: new Date().toISOString() |
138 | } | 89 | } |
139 | 90 | ||
140 | return jsig.promises.sign(data, options) | 91 | const [ documentHash, optionsHash ] = await Promise.all([ |
92 | createDocWithoutSignatureHash(data), | ||
93 | createSignatureHash(signature) | ||
94 | ]) | ||
95 | |||
96 | const toSign = optionsHash + documentHash | ||
97 | |||
98 | const sign = createSign('RSA-SHA256') | ||
99 | sign.update(toSign, 'utf8') | ||
100 | |||
101 | const signatureValue = sign.sign(byActor.privateKey, 'base64') | ||
102 | Object.assign(signature, { signatureValue }) | ||
103 | |||
104 | return Object.assign(data, { signature }) | ||
141 | } | 105 | } |
142 | 106 | ||
143 | // --------------------------------------------------------------------------- | 107 | // --------------------------------------------------------------------------- |
@@ -154,3 +118,35 @@ export { | |||
154 | } | 118 | } |
155 | 119 | ||
156 | // --------------------------------------------------------------------------- | 120 | // --------------------------------------------------------------------------- |
121 | |||
122 | function hash (obj: any): Promise<any> { | ||
123 | return jsonld.promises | ||
124 | .normalize(obj, { | ||
125 | algorithm: 'URDNA2015', | ||
126 | format: 'application/n-quads' | ||
127 | }) | ||
128 | .then(res => sha256(res)) | ||
129 | } | ||
130 | |||
131 | function createSignatureHash (signature: any) { | ||
132 | const signatureCopy = cloneDeep(signature) | ||
133 | Object.assign(signatureCopy, { | ||
134 | '@context': [ | ||
135 | 'https://w3id.org/security/v1', | ||
136 | { RsaSignature2017: 'https://w3id.org/security#RsaSignature2017' } | ||
137 | ] | ||
138 | }) | ||
139 | |||
140 | delete signatureCopy.type | ||
141 | delete signatureCopy.id | ||
142 | delete signatureCopy.signatureValue | ||
143 | |||
144 | return hash(signatureCopy) | ||
145 | } | ||
146 | |||
147 | function createDocWithoutSignatureHash (doc: any) { | ||
148 | const docWithoutSignature = cloneDeep(doc) | ||
149 | delete docWithoutSignature.signature | ||
150 | |||
151 | return hash(docWithoutSignature) | ||
152 | } | ||
diff --git a/server/helpers/utils.ts b/server/helpers/utils.ts index 1464b1477..ba07eaaf3 100644 --- a/server/helpers/utils.ts +++ b/server/helpers/utils.ts | |||
@@ -19,7 +19,10 @@ async function generateRandomString (size: number) { | |||
19 | return raw.toString('hex') | 19 | return raw.toString('hex') |
20 | } | 20 | } |
21 | 21 | ||
22 | interface FormattableToJSON<U, V> { toFormattedJSON (args?: U): V } | 22 | interface FormattableToJSON<U, V> { |
23 | toFormattedJSON (args?: U): V | ||
24 | } | ||
25 | |||
23 | function getFormattedObjects<U, V, T extends FormattableToJSON<U, V>> (objects: T[], objectsTotal: number, formattedArg?: U) { | 26 | function getFormattedObjects<U, V, T extends FormattableToJSON<U, V>> (objects: T[], objectsTotal: number, formattedArg?: U) { |
24 | const formattedObjects = objects.map(o => o.toFormattedJSON(formattedArg)) | 27 | const formattedObjects = objects.map(o => o.toFormattedJSON(formattedArg)) |
25 | 28 | ||
diff --git a/server/helpers/video.ts b/server/helpers/video.ts index c90fe06c7..d066e2b1f 100644 --- a/server/helpers/video.ts +++ b/server/helpers/video.ts | |||
@@ -1,8 +1,30 @@ | |||
1 | import { VideoModel } from '../models/video/video' | 1 | import { VideoModel } from '../models/video/video' |
2 | import * as Bluebird from 'bluebird' | ||
3 | import { | ||
4 | MVideoAccountLightBlacklistAllFiles, | ||
5 | MVideoFullLight, | ||
6 | MVideoIdThumbnail, | ||
7 | MVideoThumbnail, | ||
8 | MVideoWithRights | ||
9 | } from '@server/typings/models' | ||
10 | import { Response } from 'express' | ||
2 | 11 | ||
3 | type VideoFetchType = 'all' | 'only-video' | 'only-video-with-rights' | 'id' | 'none' | 12 | type VideoFetchType = 'all' | 'only-video' | 'only-video-with-rights' | 'id' | 'none' |
4 | 13 | ||
5 | function fetchVideo (id: number | string, fetchType: VideoFetchType, userId?: number) { | 14 | function fetchVideo (id: number | string, fetchType: 'all', userId?: number): Bluebird<MVideoFullLight> |
15 | function fetchVideo (id: number | string, fetchType: 'only-video', userId?: number): Bluebird<MVideoThumbnail> | ||
16 | function fetchVideo (id: number | string, fetchType: 'only-video-with-rights', userId?: number): Bluebird<MVideoWithRights> | ||
17 | function fetchVideo (id: number | string, fetchType: 'id' | 'none', userId?: number): Bluebird<MVideoIdThumbnail> | ||
18 | function fetchVideo ( | ||
19 | id: number | string, | ||
20 | fetchType: VideoFetchType, | ||
21 | userId?: number | ||
22 | ): Bluebird<MVideoFullLight | MVideoThumbnail | MVideoWithRights | MVideoIdThumbnail> | ||
23 | function fetchVideo ( | ||
24 | id: number | string, | ||
25 | fetchType: VideoFetchType, | ||
26 | userId?: number | ||
27 | ): Bluebird<MVideoFullLight | MVideoThumbnail | MVideoWithRights | MVideoIdThumbnail> { | ||
6 | if (fetchType === 'all') return VideoModel.loadAndPopulateAccountAndServerAndTags(id, undefined, userId) | 28 | if (fetchType === 'all') return VideoModel.loadAndPopulateAccountAndServerAndTags(id, undefined, userId) |
7 | 29 | ||
8 | if (fetchType === 'only-video-with-rights') return VideoModel.loadWithRights(id) | 30 | if (fetchType === 'only-video-with-rights') return VideoModel.loadWithRights(id) |
@@ -13,15 +35,29 @@ function fetchVideo (id: number | string, fetchType: VideoFetchType, userId?: nu | |||
13 | } | 35 | } |
14 | 36 | ||
15 | type VideoFetchByUrlType = 'all' | 'only-video' | 37 | type VideoFetchByUrlType = 'all' | 'only-video' |
16 | function fetchVideoByUrl (url: string, fetchType: VideoFetchByUrlType) { | 38 | |
39 | function fetchVideoByUrl (url: string, fetchType: 'all'): Bluebird<MVideoAccountLightBlacklistAllFiles> | ||
40 | function fetchVideoByUrl (url: string, fetchType: 'only-video'): Bluebird<MVideoThumbnail> | ||
41 | function fetchVideoByUrl (url: string, fetchType: VideoFetchByUrlType): Bluebird<MVideoAccountLightBlacklistAllFiles | MVideoThumbnail> | ||
42 | function fetchVideoByUrl (url: string, fetchType: VideoFetchByUrlType): Bluebird<MVideoAccountLightBlacklistAllFiles | MVideoThumbnail> { | ||
17 | if (fetchType === 'all') return VideoModel.loadByUrlAndPopulateAccount(url) | 43 | if (fetchType === 'all') return VideoModel.loadByUrlAndPopulateAccount(url) |
18 | 44 | ||
19 | if (fetchType === 'only-video') return VideoModel.loadByUrl(url) | 45 | if (fetchType === 'only-video') return VideoModel.loadByUrl(url) |
20 | } | 46 | } |
21 | 47 | ||
48 | function getVideo (res: Response) { | ||
49 | return res.locals.videoAll || res.locals.onlyVideo || res.locals.onlyVideoWithRights || res.locals.videoId | ||
50 | } | ||
51 | |||
52 | function getVideoWithAttributes (res: Response) { | ||
53 | return res.locals.videoAll || res.locals.onlyVideo || res.locals.onlyVideoWithRights | ||
54 | } | ||
55 | |||
22 | export { | 56 | export { |
23 | VideoFetchType, | 57 | VideoFetchType, |
24 | VideoFetchByUrlType, | 58 | VideoFetchByUrlType, |
25 | fetchVideo, | 59 | fetchVideo, |
60 | getVideo, | ||
61 | getVideoWithAttributes, | ||
26 | fetchVideoByUrl | 62 | fetchVideoByUrl |
27 | } | 63 | } |
diff --git a/server/helpers/webfinger.ts b/server/helpers/webfinger.ts index d1229e28f..5443a266b 100644 --- a/server/helpers/webfinger.ts +++ b/server/helpers/webfinger.ts | |||
@@ -4,6 +4,7 @@ import { ActorModel } from '../models/activitypub/actor' | |||
4 | import { isTestInstance } from './core-utils' | 4 | import { isTestInstance } from './core-utils' |
5 | import { isActivityPubUrlValid } from './custom-validators/activitypub/misc' | 5 | import { isActivityPubUrlValid } from './custom-validators/activitypub/misc' |
6 | import { WEBSERVER } from '../initializers/constants' | 6 | import { WEBSERVER } from '../initializers/constants' |
7 | import { MActorFull } from '../typings/models' | ||
7 | 8 | ||
8 | const webfinger = new WebFinger({ | 9 | const webfinger = new WebFinger({ |
9 | webfist_fallback: false, | 10 | webfist_fallback: false, |
@@ -17,7 +18,7 @@ async function loadActorUrlOrGetFromWebfinger (uriArg: string) { | |||
17 | const uri = uriArg.startsWith('@') ? uriArg.slice(1) : uriArg | 18 | const uri = uriArg.startsWith('@') ? uriArg.slice(1) : uriArg |
18 | 19 | ||
19 | const [ name, host ] = uri.split('@') | 20 | const [ name, host ] = uri.split('@') |
20 | let actor: ActorModel | 21 | let actor: MActorFull |
21 | 22 | ||
22 | if (!host || host === WEBSERVER.HOST) { | 23 | if (!host || host === WEBSERVER.HOST) { |
23 | actor = await ActorModel.loadLocalByName(name) | 24 | actor = await ActorModel.loadLocalByName(name) |
diff --git a/server/initializers/config.ts b/server/initializers/config.ts index 510f7d64d..164d714d6 100644 --- a/server/initializers/config.ts +++ b/server/initializers/config.ts | |||
@@ -209,6 +209,19 @@ const CONFIG = { | |||
209 | get SHORT_DESCRIPTION () { return config.get<string>('instance.short_description') }, | 209 | get SHORT_DESCRIPTION () { return config.get<string>('instance.short_description') }, |
210 | get DESCRIPTION () { return config.get<string>('instance.description') }, | 210 | get DESCRIPTION () { return config.get<string>('instance.description') }, |
211 | get TERMS () { return config.get<string>('instance.terms') }, | 211 | get TERMS () { return config.get<string>('instance.terms') }, |
212 | get CODE_OF_CONDUCT () { return config.get<string>('instance.code_of_conduct') }, | ||
213 | |||
214 | get CREATION_REASON () { return config.get<string>('instance.creation_reason') }, | ||
215 | |||
216 | get MODERATION_INFORMATION () { return config.get<string>('instance.moderation_information') }, | ||
217 | get ADMINISTRATOR () { return config.get<string>('instance.administrator') }, | ||
218 | get MAINTENANCE_LIFETIME () { return config.get<string>('instance.maintenance_lifetime') }, | ||
219 | get BUSINESS_MODEL () { return config.get<string>('instance.business_model') }, | ||
220 | get HARDWARE_INFORMATION () { return config.get<string>('instance.hardware_information') }, | ||
221 | |||
222 | get LANGUAGES () { return config.get<string[]>('instance.languages') || [] }, | ||
223 | get CATEGORIES () { return config.get<number[]>('instance.categories') || [] }, | ||
224 | |||
212 | get IS_NSFW () { return config.get<boolean>('instance.is_nsfw') }, | 225 | get IS_NSFW () { return config.get<boolean>('instance.is_nsfw') }, |
213 | get DEFAULT_CLIENT_ROUTE () { return config.get<string>('instance.default_client_route') }, | 226 | get DEFAULT_CLIENT_ROUTE () { return config.get<string>('instance.default_client_route') }, |
214 | get DEFAULT_NSFW_POLICY () { return config.get<NSFWPolicyType>('instance.default_nsfw_policy') }, | 227 | get DEFAULT_NSFW_POLICY () { return config.get<NSFWPolicyType>('instance.default_nsfw_policy') }, |
@@ -232,6 +245,23 @@ const CONFIG = { | |||
232 | get MANUAL_APPROVAL () { return config.get<boolean>('followers.instance.manual_approval') } | 245 | get MANUAL_APPROVAL () { return config.get<boolean>('followers.instance.manual_approval') } |
233 | } | 246 | } |
234 | }, | 247 | }, |
248 | FOLLOWINGS: { | ||
249 | INSTANCE: { | ||
250 | AUTO_FOLLOW_BACK: { | ||
251 | get ENABLED () { | ||
252 | return config.get<boolean>('followings.instance.auto_follow_back.enabled') | ||
253 | } | ||
254 | }, | ||
255 | AUTO_FOLLOW_INDEX: { | ||
256 | get ENABLED () { | ||
257 | return config.get<boolean>('followings.instance.auto_follow_index.enabled') | ||
258 | }, | ||
259 | get INDEX_URL () { | ||
260 | return config.get<string>('followings.instance.auto_follow_index.index_url') | ||
261 | } | ||
262 | } | ||
263 | } | ||
264 | }, | ||
235 | THEME: { | 265 | THEME: { |
236 | get DEFAULT () { return config.get<string>('theme.default') } | 266 | get DEFAULT () { return config.get<string>('theme.default') } |
237 | } | 267 | } |
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index 3dc178b11..be4a66488 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts | |||
@@ -14,7 +14,7 @@ import { CONFIG, registerConfigChangedHandler } from './config' | |||
14 | 14 | ||
15 | // --------------------------------------------------------------------------- | 15 | // --------------------------------------------------------------------------- |
16 | 16 | ||
17 | const LAST_MIGRATION_VERSION = 420 | 17 | const LAST_MIGRATION_VERSION = 435 |
18 | 18 | ||
19 | // --------------------------------------------------------------------------- | 19 | // --------------------------------------------------------------------------- |
20 | 20 | ||
@@ -168,10 +168,15 @@ const SCHEDULER_INTERVALS_MS = { | |||
168 | updateVideos: 60000, // 1 minute | 168 | updateVideos: 60000, // 1 minute |
169 | youtubeDLUpdate: 60000 * 60 * 24, // 1 day | 169 | youtubeDLUpdate: 60000 * 60 * 24, // 1 day |
170 | checkPlugins: CONFIG.PLUGINS.INDEX.CHECK_LATEST_VERSIONS_INTERVAL, | 170 | checkPlugins: CONFIG.PLUGINS.INDEX.CHECK_LATEST_VERSIONS_INTERVAL, |
171 | autoFollowIndexInstances: 60000 * 60 * 24, // 1 day | ||
171 | removeOldViews: 60000 * 60 * 24, // 1 day | 172 | removeOldViews: 60000 * 60 * 24, // 1 day |
172 | removeOldHistory: 60000 * 60 * 24 // 1 day | 173 | removeOldHistory: 60000 * 60 * 24 // 1 day |
173 | } | 174 | } |
174 | 175 | ||
176 | const INSTANCES_INDEX = { | ||
177 | HOSTS_PATH: '/api/v1/instances/hosts' | ||
178 | } | ||
179 | |||
175 | // --------------------------------------------------------------------------- | 180 | // --------------------------------------------------------------------------- |
176 | 181 | ||
177 | const CONSTRAINTS_FIELDS = { | 182 | const CONSTRAINTS_FIELDS = { |
@@ -633,6 +638,7 @@ if (isTestInstance() === true) { | |||
633 | SCHEDULER_INTERVALS_MS.removeOldHistory = 5000 | 638 | SCHEDULER_INTERVALS_MS.removeOldHistory = 5000 |
634 | SCHEDULER_INTERVALS_MS.removeOldViews = 5000 | 639 | SCHEDULER_INTERVALS_MS.removeOldViews = 5000 |
635 | SCHEDULER_INTERVALS_MS.updateVideos = 5000 | 640 | SCHEDULER_INTERVALS_MS.updateVideos = 5000 |
641 | SCHEDULER_INTERVALS_MS.autoFollowIndexInstances = 5000 | ||
636 | REPEAT_JOBS[ 'videos-views' ] = { every: 5000 } | 642 | REPEAT_JOBS[ 'videos-views' ] = { every: 5000 } |
637 | 643 | ||
638 | REDUNDANCY.VIDEOS.RANDOMIZED_FACTOR = 1 | 644 | REDUNDANCY.VIDEOS.RANDOMIZED_FACTOR = 1 |
@@ -683,6 +689,7 @@ export { | |||
683 | PREVIEWS_SIZE, | 689 | PREVIEWS_SIZE, |
684 | REMOTE_SCHEME, | 690 | REMOTE_SCHEME, |
685 | FOLLOW_STATES, | 691 | FOLLOW_STATES, |
692 | INSTANCES_INDEX, | ||
686 | DEFAULT_USER_THEME_NAME, | 693 | DEFAULT_USER_THEME_NAME, |
687 | SERVER_ACTOR_NAME, | 694 | SERVER_ACTOR_NAME, |
688 | PLUGIN_GLOBAL_CSS_FILE_NAME, | 695 | PLUGIN_GLOBAL_CSS_FILE_NAME, |
diff --git a/server/initializers/migrations/0425-nullable-actor-fields.ts b/server/initializers/migrations/0425-nullable-actor-fields.ts new file mode 100644 index 000000000..4e5f9e6ab --- /dev/null +++ b/server/initializers/migrations/0425-nullable-actor-fields.ts | |||
@@ -0,0 +1,26 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | |||
3 | async function up (utils: { | ||
4 | transaction: Sequelize.Transaction, | ||
5 | queryInterface: Sequelize.QueryInterface, | ||
6 | sequelize: Sequelize.Sequelize, | ||
7 | db: any | ||
8 | }): Promise<void> { | ||
9 | const data = { | ||
10 | type: Sequelize.STRING, | ||
11 | allowNull: true | ||
12 | } | ||
13 | |||
14 | await utils.queryInterface.changeColumn('actor', 'outboxUrl', data) | ||
15 | await utils.queryInterface.changeColumn('actor', 'followersUrl', data) | ||
16 | await utils.queryInterface.changeColumn('actor', 'followingUrl', data) | ||
17 | } | ||
18 | |||
19 | function down (options) { | ||
20 | throw new Error('Not implemented.') | ||
21 | } | ||
22 | |||
23 | export { | ||
24 | up, | ||
25 | down | ||
26 | } | ||
diff --git a/server/initializers/migrations/0430-auto-follow-notification-setting.ts b/server/initializers/migrations/0430-auto-follow-notification-setting.ts new file mode 100644 index 000000000..034bdd46d --- /dev/null +++ b/server/initializers/migrations/0430-auto-follow-notification-setting.ts | |||
@@ -0,0 +1,40 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | |||
3 | async function up (utils: { | ||
4 | transaction: Sequelize.Transaction, | ||
5 | queryInterface: Sequelize.QueryInterface, | ||
6 | sequelize: Sequelize.Sequelize, | ||
7 | db: any | ||
8 | }): Promise<void> { | ||
9 | { | ||
10 | const data = { | ||
11 | type: Sequelize.INTEGER, | ||
12 | defaultValue: null, | ||
13 | allowNull: true | ||
14 | } | ||
15 | await utils.queryInterface.addColumn('userNotificationSetting', 'autoInstanceFollowing', data) | ||
16 | } | ||
17 | |||
18 | { | ||
19 | const query = 'UPDATE "userNotificationSetting" SET "autoInstanceFollowing" = 1' | ||
20 | await utils.sequelize.query(query) | ||
21 | } | ||
22 | |||
23 | { | ||
24 | const data = { | ||
25 | type: Sequelize.INTEGER, | ||
26 | defaultValue: null, | ||
27 | allowNull: false | ||
28 | } | ||
29 | await utils.queryInterface.changeColumn('userNotificationSetting', 'autoInstanceFollowing', data) | ||
30 | } | ||
31 | } | ||
32 | |||
33 | function down (options) { | ||
34 | throw new Error('Not implemented.') | ||
35 | } | ||
36 | |||
37 | export { | ||
38 | up, | ||
39 | down | ||
40 | } | ||
diff --git a/server/initializers/migrations/0435-user-modals.ts b/server/initializers/migrations/0435-user-modals.ts new file mode 100644 index 000000000..5c2aa85b5 --- /dev/null +++ b/server/initializers/migrations/0435-user-modals.ts | |||
@@ -0,0 +1,40 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | |||
3 | async function up (utils: { | ||
4 | transaction: Sequelize.Transaction, | ||
5 | queryInterface: Sequelize.QueryInterface, | ||
6 | sequelize: Sequelize.Sequelize, | ||
7 | db: any | ||
8 | }): Promise<void> { | ||
9 | { | ||
10 | const data = { | ||
11 | type: Sequelize.BOOLEAN, | ||
12 | allowNull: false, | ||
13 | defaultValue: false | ||
14 | } | ||
15 | |||
16 | await utils.queryInterface.addColumn('user', 'noInstanceConfigWarningModal', data) | ||
17 | } | ||
18 | |||
19 | { | ||
20 | const data = { | ||
21 | type: Sequelize.BOOLEAN, | ||
22 | allowNull: false, | ||
23 | defaultValue: true | ||
24 | } | ||
25 | |||
26 | await utils.queryInterface.addColumn('user', 'noWelcomeModal', data) | ||
27 | data.defaultValue = false | ||
28 | |||
29 | await utils.queryInterface.changeColumn('user', 'noWelcomeModal', data) | ||
30 | } | ||
31 | } | ||
32 | |||
33 | function down (options) { | ||
34 | throw new Error('Not implemented.') | ||
35 | } | ||
36 | |||
37 | export { | ||
38 | up, | ||
39 | down | ||
40 | } | ||
diff --git a/server/lib/activitypub/actor.ts b/server/lib/activitypub/actor.ts index 9f5d12eb4..13b73077e 100644 --- a/server/lib/activitypub/actor.ts +++ b/server/lib/activitypub/actor.ts | |||
@@ -22,13 +22,27 @@ import { JobQueue } from '../job-queue' | |||
22 | import { getServerActor } from '../../helpers/utils' | 22 | import { getServerActor } from '../../helpers/utils' |
23 | import { ActorFetchByUrlType, fetchActorByUrl } from '../../helpers/actor' | 23 | import { ActorFetchByUrlType, fetchActorByUrl } from '../../helpers/actor' |
24 | import { sequelizeTypescript } from '../../initializers/database' | 24 | import { sequelizeTypescript } from '../../initializers/database' |
25 | import { | ||
26 | MAccount, | ||
27 | MAccountDefault, | ||
28 | MActor, | ||
29 | MActorAccountChannelId, | ||
30 | MActorAccountChannelIdActor, | ||
31 | MActorAccountId, | ||
32 | MActorDefault, | ||
33 | MActorFull, | ||
34 | MActorFullActor, | ||
35 | MActorId, | ||
36 | MChannel, | ||
37 | MChannelAccountDefault | ||
38 | } from '../../typings/models' | ||
25 | 39 | ||
26 | // Set account keys, this could be long so process after the account creation and do not block the client | 40 | // Set account keys, this could be long so process after the account creation and do not block the client |
27 | function setAsyncActorKeys (actor: ActorModel) { | 41 | function setAsyncActorKeys <T extends MActor> (actor: T) { |
28 | return createPrivateAndPublicKeys() | 42 | return createPrivateAndPublicKeys() |
29 | .then(({ publicKey, privateKey }) => { | 43 | .then(({ publicKey, privateKey }) => { |
30 | actor.set('publicKey', publicKey) | 44 | actor.publicKey = publicKey |
31 | actor.set('privateKey', privateKey) | 45 | actor.privateKey = privateKey |
32 | return actor.save() | 46 | return actor.save() |
33 | }) | 47 | }) |
34 | .catch(err => { | 48 | .catch(err => { |
@@ -37,12 +51,26 @@ function setAsyncActorKeys (actor: ActorModel) { | |||
37 | }) | 51 | }) |
38 | } | 52 | } |
39 | 53 | ||
54 | function getOrCreateActorAndServerAndModel ( | ||
55 | activityActor: string | ActivityPubActor, | ||
56 | fetchType: 'all', | ||
57 | recurseIfNeeded?: boolean, | ||
58 | updateCollections?: boolean | ||
59 | ): Promise<MActorFullActor> | ||
60 | |||
61 | function getOrCreateActorAndServerAndModel ( | ||
62 | activityActor: string | ActivityPubActor, | ||
63 | fetchType?: 'association-ids', | ||
64 | recurseIfNeeded?: boolean, | ||
65 | updateCollections?: boolean | ||
66 | ): Promise<MActorAccountChannelId> | ||
67 | |||
40 | async function getOrCreateActorAndServerAndModel ( | 68 | async function getOrCreateActorAndServerAndModel ( |
41 | activityActor: string | ActivityPubActor, | 69 | activityActor: string | ActivityPubActor, |
42 | fetchType: ActorFetchByUrlType = 'actor-and-association-ids', | 70 | fetchType: ActorFetchByUrlType = 'association-ids', |
43 | recurseIfNeeded = true, | 71 | recurseIfNeeded = true, |
44 | updateCollections = false | 72 | updateCollections = false |
45 | ) { | 73 | ): Promise<MActorFullActor | MActorAccountChannelId> { |
46 | const actorUrl = getAPId(activityActor) | 74 | const actorUrl = getAPId(activityActor) |
47 | let created = false | 75 | let created = false |
48 | let accountPlaylistsUrl: string | 76 | let accountPlaylistsUrl: string |
@@ -61,7 +89,7 @@ async function getOrCreateActorAndServerAndModel ( | |||
61 | 89 | ||
62 | // Create the attributed to actor | 90 | // Create the attributed to actor |
63 | // In PeerTube a video channel is owned by an account | 91 | // In PeerTube a video channel is owned by an account |
64 | let ownerActor: ActorModel = undefined | 92 | let ownerActor: MActorFullActor |
65 | if (recurseIfNeeded === true && result.actor.type === 'Group') { | 93 | if (recurseIfNeeded === true && result.actor.type === 'Group') { |
66 | const accountAttributedTo = result.attributedTo.find(a => a.type === 'Person') | 94 | const accountAttributedTo = result.attributedTo.find(a => a.type === 'Person') |
67 | if (!accountAttributedTo) throw new Error('Cannot find account attributed to video channel ' + actor.url) | 95 | if (!accountAttributedTo) throw new Error('Cannot find account attributed to video channel ' + actor.url) |
@@ -85,8 +113,8 @@ async function getOrCreateActorAndServerAndModel ( | |||
85 | accountPlaylistsUrl = result.playlists | 113 | accountPlaylistsUrl = result.playlists |
86 | } | 114 | } |
87 | 115 | ||
88 | if (actor.Account) actor.Account.Actor = actor | 116 | if (actor.Account) (actor as MActorAccountChannelIdActor).Account.Actor = actor |
89 | if (actor.VideoChannel) actor.VideoChannel.Actor = actor | 117 | if (actor.VideoChannel) (actor as MActorAccountChannelIdActor).VideoChannel.Actor = actor |
90 | 118 | ||
91 | const { actor: actorRefreshed, refreshed } = await retryTransactionWrapper(refreshActorIfNeeded, actor, fetchType) | 119 | const { actor: actorRefreshed, refreshed } = await retryTransactionWrapper(refreshActorIfNeeded, actor, fetchType) |
92 | if (!actorRefreshed) throw new Error('Actor ' + actorRefreshed.url + ' does not exist anymore.') | 120 | if (!actorRefreshed) throw new Error('Actor ' + actorRefreshed.url + ' does not exist anymore.') |
@@ -120,7 +148,7 @@ function buildActorInstance (type: ActivityPubActorType, url: string, preferredU | |||
120 | sharedInboxUrl: WEBSERVER.URL + '/inbox', | 148 | sharedInboxUrl: WEBSERVER.URL + '/inbox', |
121 | followersUrl: url + '/followers', | 149 | followersUrl: url + '/followers', |
122 | followingUrl: url + '/following' | 150 | followingUrl: url + '/following' |
123 | }) | 151 | }) as MActor |
124 | } | 152 | } |
125 | 153 | ||
126 | async function updateActorInstance (actorInstance: ActorModel, attributes: ActivityPubActor) { | 154 | async function updateActorInstance (actorInstance: ActorModel, attributes: ActivityPubActor) { |
@@ -140,7 +168,8 @@ async function updateActorInstance (actorInstance: ActorModel, attributes: Activ | |||
140 | actorInstance.followingUrl = attributes.following | 168 | actorInstance.followingUrl = attributes.following |
141 | } | 169 | } |
142 | 170 | ||
143 | async function updateActorAvatarInstance (actor: ActorModel, info: { name: string, onDisk: boolean, fileUrl: string }, t: Transaction) { | 171 | type AvatarInfo = { name: string, onDisk: boolean, fileUrl: string } |
172 | async function updateActorAvatarInstance (actor: MActorDefault, info: AvatarInfo, t: Transaction) { | ||
144 | if (info.name !== undefined) { | 173 | if (info.name !== undefined) { |
145 | if (actor.avatarId) { | 174 | if (actor.avatarId) { |
146 | try { | 175 | try { |
@@ -212,14 +241,16 @@ async function addFetchOutboxJob (actor: Pick<ActorModel, 'id' | 'outboxUrl'>) { | |||
212 | return JobQueue.Instance.createJob({ type: 'activitypub-http-fetcher', payload }) | 241 | return JobQueue.Instance.createJob({ type: 'activitypub-http-fetcher', payload }) |
213 | } | 242 | } |
214 | 243 | ||
215 | async function refreshActorIfNeeded ( | 244 | async function refreshActorIfNeeded <T extends MActorFull | MActorAccountChannelId> ( |
216 | actorArg: ActorModel, | 245 | actorArg: T, |
217 | fetchedType: ActorFetchByUrlType | 246 | fetchedType: ActorFetchByUrlType |
218 | ): Promise<{ actor: ActorModel, refreshed: boolean }> { | 247 | ): Promise<{ actor: T | MActorFull, refreshed: boolean }> { |
219 | if (!actorArg.isOutdated()) return { actor: actorArg, refreshed: false } | 248 | if (!actorArg.isOutdated()) return { actor: actorArg, refreshed: false } |
220 | 249 | ||
221 | // We need more attributes | 250 | // We need more attributes |
222 | const actor = fetchedType === 'all' ? actorArg : await ActorModel.loadByUrlAndPopulateAccountAndChannel(actorArg.url) | 251 | const actor = fetchedType === 'all' |
252 | ? actorArg as MActorFull | ||
253 | : await ActorModel.loadByUrlAndPopulateAccountAndChannel(actorArg.url) | ||
223 | 254 | ||
224 | try { | 255 | try { |
225 | let actorUrl: string | 256 | let actorUrl: string |
@@ -297,9 +328,9 @@ export { | |||
297 | 328 | ||
298 | function saveActorAndServerAndModelIfNotExist ( | 329 | function saveActorAndServerAndModelIfNotExist ( |
299 | result: FetchRemoteActorResult, | 330 | result: FetchRemoteActorResult, |
300 | ownerActor?: ActorModel, | 331 | ownerActor?: MActorFullActor, |
301 | t?: Transaction | 332 | t?: Transaction |
302 | ): Bluebird<ActorModel> | Promise<ActorModel> { | 333 | ): Bluebird<MActorFullActor> | Promise<MActorFullActor> { |
303 | let actor = result.actor | 334 | let actor = result.actor |
304 | 335 | ||
305 | if (t !== undefined) return save(t) | 336 | if (t !== undefined) return save(t) |
@@ -336,7 +367,7 @@ function saveActorAndServerAndModelIfNotExist ( | |||
336 | 367 | ||
337 | // Force the actor creation, sometimes Sequelize skips the save() when it thinks the instance already exists | 368 | // Force the actor creation, sometimes Sequelize skips the save() when it thinks the instance already exists |
338 | // (which could be false in a retried query) | 369 | // (which could be false in a retried query) |
339 | const [ actorCreated ] = await ActorModel.findOrCreate({ | 370 | const [ actorCreated ] = await ActorModel.findOrCreate<MActorFullActor>({ |
340 | defaults: actor.toJSON(), | 371 | defaults: actor.toJSON(), |
341 | where: { | 372 | where: { |
342 | url: actor.url | 373 | url: actor.url |
@@ -345,12 +376,11 @@ function saveActorAndServerAndModelIfNotExist ( | |||
345 | }) | 376 | }) |
346 | 377 | ||
347 | if (actorCreated.type === 'Person' || actorCreated.type === 'Application') { | 378 | if (actorCreated.type === 'Person' || actorCreated.type === 'Application') { |
348 | actorCreated.Account = await saveAccount(actorCreated, result, t) | 379 | actorCreated.Account = await saveAccount(actorCreated, result, t) as MAccountDefault |
349 | actorCreated.Account.Actor = actorCreated | 380 | actorCreated.Account.Actor = actorCreated |
350 | } else if (actorCreated.type === 'Group') { // Video channel | 381 | } else if (actorCreated.type === 'Group') { // Video channel |
351 | actorCreated.VideoChannel = await saveVideoChannel(actorCreated, result, ownerActor, t) | 382 | const channel = await saveVideoChannel(actorCreated, result, ownerActor, t) |
352 | actorCreated.VideoChannel.Actor = actorCreated | 383 | actorCreated.VideoChannel = Object.assign(channel, { Actor: actorCreated, Account: ownerActor.Account }) |
353 | actorCreated.VideoChannel.Account = ownerActor.Account | ||
354 | } | 384 | } |
355 | 385 | ||
356 | actorCreated.Server = server | 386 | actorCreated.Server = server |
@@ -360,7 +390,7 @@ function saveActorAndServerAndModelIfNotExist ( | |||
360 | } | 390 | } |
361 | 391 | ||
362 | type FetchRemoteActorResult = { | 392 | type FetchRemoteActorResult = { |
363 | actor: ActorModel | 393 | actor: MActor |
364 | name: string | 394 | name: string |
365 | summary: string | 395 | summary: string |
366 | support?: string | 396 | support?: string |
@@ -429,7 +459,7 @@ async function fetchRemoteActor (actorUrl: string): Promise<{ statusCode?: numbe | |||
429 | } | 459 | } |
430 | } | 460 | } |
431 | 461 | ||
432 | async function saveAccount (actor: ActorModel, result: FetchRemoteActorResult, t: Transaction) { | 462 | async function saveAccount (actor: MActorId, result: FetchRemoteActorResult, t: Transaction) { |
433 | const [ accountCreated ] = await AccountModel.findOrCreate({ | 463 | const [ accountCreated ] = await AccountModel.findOrCreate({ |
434 | defaults: { | 464 | defaults: { |
435 | name: result.name, | 465 | name: result.name, |
@@ -442,10 +472,10 @@ async function saveAccount (actor: ActorModel, result: FetchRemoteActorResult, t | |||
442 | transaction: t | 472 | transaction: t |
443 | }) | 473 | }) |
444 | 474 | ||
445 | return accountCreated | 475 | return accountCreated as MAccount |
446 | } | 476 | } |
447 | 477 | ||
448 | async function saveVideoChannel (actor: ActorModel, result: FetchRemoteActorResult, ownerActor: ActorModel, t: Transaction) { | 478 | async function saveVideoChannel (actor: MActorId, result: FetchRemoteActorResult, ownerActor: MActorAccountId, t: Transaction) { |
449 | const [ videoChannelCreated ] = await VideoChannelModel.findOrCreate({ | 479 | const [ videoChannelCreated ] = await VideoChannelModel.findOrCreate({ |
450 | defaults: { | 480 | defaults: { |
451 | name: result.name, | 481 | name: result.name, |
@@ -460,5 +490,5 @@ async function saveVideoChannel (actor: ActorModel, result: FetchRemoteActorResu | |||
460 | transaction: t | 490 | transaction: t |
461 | }) | 491 | }) |
462 | 492 | ||
463 | return videoChannelCreated | 493 | return videoChannelCreated as MChannel |
464 | } | 494 | } |
diff --git a/server/lib/activitypub/audience.ts b/server/lib/activitypub/audience.ts index 0e3d78590..f2ab54cf7 100644 --- a/server/lib/activitypub/audience.ts +++ b/server/lib/activitypub/audience.ts | |||
@@ -3,11 +3,10 @@ import { ActivityAudience } from '../../../shared/models/activitypub' | |||
3 | import { ACTIVITY_PUB } from '../../initializers/constants' | 3 | import { ACTIVITY_PUB } from '../../initializers/constants' |
4 | import { ActorModel } from '../../models/activitypub/actor' | 4 | import { ActorModel } from '../../models/activitypub/actor' |
5 | import { VideoModel } from '../../models/video/video' | 5 | import { VideoModel } from '../../models/video/video' |
6 | import { VideoCommentModel } from '../../models/video/video-comment' | ||
7 | import { VideoShareModel } from '../../models/video/video-share' | 6 | import { VideoShareModel } from '../../models/video/video-share' |
8 | import { ActorModelOnly } from '../../typings/models' | 7 | import { MActorFollowersUrl, MActorLight, MCommentOwner, MCommentOwnerVideo, MVideo, MVideoAccountLight } from '../../typings/models' |
9 | 8 | ||
10 | function getRemoteVideoAudience (video: VideoModel, actorsInvolvedInVideo: ActorModel[]): ActivityAudience { | 9 | function getRemoteVideoAudience (video: MVideoAccountLight, actorsInvolvedInVideo: MActorFollowersUrl[]): ActivityAudience { |
11 | return { | 10 | return { |
12 | to: [ video.VideoChannel.Account.Actor.url ], | 11 | to: [ video.VideoChannel.Account.Actor.url ], |
13 | cc: actorsInvolvedInVideo.map(a => a.followersUrl) | 12 | cc: actorsInvolvedInVideo.map(a => a.followersUrl) |
@@ -15,9 +14,9 @@ function getRemoteVideoAudience (video: VideoModel, actorsInvolvedInVideo: Actor | |||
15 | } | 14 | } |
16 | 15 | ||
17 | function getVideoCommentAudience ( | 16 | function getVideoCommentAudience ( |
18 | videoComment: VideoCommentModel, | 17 | videoComment: MCommentOwnerVideo, |
19 | threadParentComments: VideoCommentModel[], | 18 | threadParentComments: MCommentOwner[], |
20 | actorsInvolvedInVideo: ActorModel[], | 19 | actorsInvolvedInVideo: MActorFollowersUrl[], |
21 | isOrigin = false | 20 | isOrigin = false |
22 | ): ActivityAudience { | 21 | ): ActivityAudience { |
23 | const to = [ ACTIVITY_PUB.PUBLIC ] | 22 | const to = [ ACTIVITY_PUB.PUBLIC ] |
@@ -42,26 +41,28 @@ function getVideoCommentAudience ( | |||
42 | } | 41 | } |
43 | } | 42 | } |
44 | 43 | ||
45 | function getAudienceFromFollowersOf (actorsInvolvedInObject: ActorModel[]): ActivityAudience { | 44 | function getAudienceFromFollowersOf (actorsInvolvedInObject: MActorFollowersUrl[]): ActivityAudience { |
46 | return { | 45 | return { |
47 | to: [ ACTIVITY_PUB.PUBLIC ].concat(actorsInvolvedInObject.map(a => a.followersUrl)), | 46 | to: [ ACTIVITY_PUB.PUBLIC ].concat(actorsInvolvedInObject.map(a => a.followersUrl)), |
48 | cc: [] | 47 | cc: [] |
49 | } | 48 | } |
50 | } | 49 | } |
51 | 50 | ||
52 | async function getActorsInvolvedInVideo (video: VideoModel, t: Transaction) { | 51 | async function getActorsInvolvedInVideo (video: MVideo, t: Transaction) { |
53 | const actors = await VideoShareModel.loadActorsByShare(video.id, t) | 52 | const actors: MActorLight[] = await VideoShareModel.loadActorsByShare(video.id, t) |
54 | 53 | ||
55 | const videoActor = video.VideoChannel && video.VideoChannel.Account | 54 | const videoAll = video as VideoModel |
56 | ? video.VideoChannel.Account.Actor | 55 | |
57 | : await ActorModel.loadAccountActorByVideoId(video.id, t) | 56 | const videoActor = videoAll.VideoChannel && videoAll.VideoChannel.Account |
57 | ? videoAll.VideoChannel.Account.Actor | ||
58 | : await ActorModel.loadFromAccountByVideoId(video.id, t) | ||
58 | 59 | ||
59 | actors.push(videoActor) | 60 | actors.push(videoActor) |
60 | 61 | ||
61 | return actors | 62 | return actors |
62 | } | 63 | } |
63 | 64 | ||
64 | function getAudience (actorSender: ActorModelOnly, isPublic = true) { | 65 | function getAudience (actorSender: MActorFollowersUrl, isPublic = true) { |
65 | return buildAudience([ actorSender.followersUrl ], isPublic) | 66 | return buildAudience([ actorSender.followersUrl ], isPublic) |
66 | } | 67 | } |
67 | 68 | ||
diff --git a/server/lib/activitypub/cache-file.ts b/server/lib/activitypub/cache-file.ts index de5cc54ac..65b2dcb49 100644 --- a/server/lib/activitypub/cache-file.ts +++ b/server/lib/activitypub/cache-file.ts | |||
@@ -1,10 +1,10 @@ | |||
1 | import { CacheFileObject } from '../../../shared/index' | 1 | import { CacheFileObject } from '../../../shared/index' |
2 | import { VideoModel } from '../../models/video/video' | ||
3 | import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy' | 2 | import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy' |
4 | import { Transaction } from 'sequelize' | 3 | import { Transaction } from 'sequelize' |
5 | import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type' | 4 | import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type' |
5 | import { MActorId, MVideoRedundancy, MVideoWithAllFiles } from '@server/typings/models' | ||
6 | 6 | ||
7 | function cacheFileActivityObjectToDBAttributes (cacheFileObject: CacheFileObject, video: VideoModel, byActor: { id?: number }) { | 7 | function cacheFileActivityObjectToDBAttributes (cacheFileObject: CacheFileObject, video: MVideoWithAllFiles, byActor: MActorId) { |
8 | 8 | ||
9 | if (cacheFileObject.url.mediaType === 'application/x-mpegURL') { | 9 | if (cacheFileObject.url.mediaType === 'application/x-mpegURL') { |
10 | const url = cacheFileObject.url | 10 | const url = cacheFileObject.url |
@@ -39,7 +39,7 @@ function cacheFileActivityObjectToDBAttributes (cacheFileObject: CacheFileObject | |||
39 | } | 39 | } |
40 | } | 40 | } |
41 | 41 | ||
42 | async function createOrUpdateCacheFile (cacheFileObject: CacheFileObject, video: VideoModel, byActor: { id?: number }, t: Transaction) { | 42 | async function createOrUpdateCacheFile (cacheFileObject: CacheFileObject, video: MVideoWithAllFiles, byActor: MActorId, t: Transaction) { |
43 | const redundancyModel = await VideoRedundancyModel.loadByUrl(cacheFileObject.id, t) | 43 | const redundancyModel = await VideoRedundancyModel.loadByUrl(cacheFileObject.id, t) |
44 | 44 | ||
45 | if (!redundancyModel) { | 45 | if (!redundancyModel) { |
@@ -49,7 +49,7 @@ async function createOrUpdateCacheFile (cacheFileObject: CacheFileObject, video: | |||
49 | } | 49 | } |
50 | } | 50 | } |
51 | 51 | ||
52 | function createCacheFile (cacheFileObject: CacheFileObject, video: VideoModel, byActor: { id?: number }, t: Transaction) { | 52 | function createCacheFile (cacheFileObject: CacheFileObject, video: MVideoWithAllFiles, byActor: MActorId, t: Transaction) { |
53 | const attributes = cacheFileActivityObjectToDBAttributes(cacheFileObject, video, byActor) | 53 | const attributes = cacheFileActivityObjectToDBAttributes(cacheFileObject, video, byActor) |
54 | 54 | ||
55 | return VideoRedundancyModel.create(attributes, { transaction: t }) | 55 | return VideoRedundancyModel.create(attributes, { transaction: t }) |
@@ -57,9 +57,9 @@ function createCacheFile (cacheFileObject: CacheFileObject, video: VideoModel, b | |||
57 | 57 | ||
58 | function updateCacheFile ( | 58 | function updateCacheFile ( |
59 | cacheFileObject: CacheFileObject, | 59 | cacheFileObject: CacheFileObject, |
60 | redundancyModel: VideoRedundancyModel, | 60 | redundancyModel: MVideoRedundancy, |
61 | video: VideoModel, | 61 | video: MVideoWithAllFiles, |
62 | byActor: { id?: number }, | 62 | byActor: MActorId, |
63 | t: Transaction | 63 | t: Transaction |
64 | ) { | 64 | ) { |
65 | if (redundancyModel.actorId !== byActor.id) { | 65 | if (redundancyModel.actorId !== byActor.id) { |
diff --git a/server/lib/activitypub/follow.ts b/server/lib/activitypub/follow.ts new file mode 100644 index 000000000..1abf43cd4 --- /dev/null +++ b/server/lib/activitypub/follow.ts | |||
@@ -0,0 +1,36 @@ | |||
1 | import { MActorFollowActors } from '../../typings/models' | ||
2 | import { CONFIG } from '../../initializers/config' | ||
3 | import { SERVER_ACTOR_NAME } from '../../initializers/constants' | ||
4 | import { JobQueue } from '../job-queue' | ||
5 | import { logger } from '../../helpers/logger' | ||
6 | import { getServerActor } from '../../helpers/utils' | ||
7 | import { ServerModel } from '../../models/server/server' | ||
8 | |||
9 | async function autoFollowBackIfNeeded (actorFollow: MActorFollowActors) { | ||
10 | if (!CONFIG.FOLLOWINGS.INSTANCE.AUTO_FOLLOW_BACK.ENABLED) return | ||
11 | |||
12 | const follower = actorFollow.ActorFollower | ||
13 | |||
14 | if (follower.type === 'Application' && follower.preferredUsername === SERVER_ACTOR_NAME) { | ||
15 | logger.info('Auto follow back %s.', follower.url) | ||
16 | |||
17 | const me = await getServerActor() | ||
18 | |||
19 | const server = await ServerModel.load(follower.serverId) | ||
20 | const host = server.host | ||
21 | |||
22 | const payload = { | ||
23 | host, | ||
24 | name: SERVER_ACTOR_NAME, | ||
25 | followerActorId: me.id, | ||
26 | isAutoFollow: true | ||
27 | } | ||
28 | |||
29 | JobQueue.Instance.createJob({ type: 'activitypub-follow', payload }) | ||
30 | .catch(err => logger.error('Cannot create auto follow back job for %s.', host, err)) | ||
31 | } | ||
32 | } | ||
33 | |||
34 | export { | ||
35 | autoFollowBackIfNeeded | ||
36 | } | ||
diff --git a/server/lib/activitypub/playlist.ts b/server/lib/activitypub/playlist.ts index c2e2a3283..c52b715ef 100644 --- a/server/lib/activitypub/playlist.ts +++ b/server/lib/activitypub/playlist.ts | |||
@@ -1,7 +1,6 @@ | |||
1 | import { PlaylistObject } from '../../../shared/models/activitypub/objects/playlist-object' | 1 | import { PlaylistObject } from '../../../shared/models/activitypub/objects/playlist-object' |
2 | import { crawlCollectionPage } from './crawl' | 2 | import { crawlCollectionPage } from './crawl' |
3 | import { ACTIVITY_PUB, CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants' | 3 | import { ACTIVITY_PUB, CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants' |
4 | import { AccountModel } from '../../models/account/account' | ||
5 | import { isArray } from '../../helpers/custom-validators/misc' | 4 | import { isArray } from '../../helpers/custom-validators/misc' |
6 | import { getOrCreateActorAndServerAndModel } from './actor' | 5 | import { getOrCreateActorAndServerAndModel } from './actor' |
7 | import { logger } from '../../helpers/logger' | 6 | import { logger } from '../../helpers/logger' |
@@ -13,14 +12,14 @@ import { PlaylistElementObject } from '../../../shared/models/activitypub/object | |||
13 | import { getOrCreateVideoAndAccountAndChannel } from './videos' | 12 | import { getOrCreateVideoAndAccountAndChannel } from './videos' |
14 | import { isPlaylistElementObjectValid, isPlaylistObjectValid } from '../../helpers/custom-validators/activitypub/playlist' | 13 | import { isPlaylistElementObjectValid, isPlaylistObjectValid } from '../../helpers/custom-validators/activitypub/playlist' |
15 | import { VideoPlaylistElementModel } from '../../models/video/video-playlist-element' | 14 | import { VideoPlaylistElementModel } from '../../models/video/video-playlist-element' |
16 | import { VideoModel } from '../../models/video/video' | ||
17 | import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model' | 15 | import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model' |
18 | import { sequelizeTypescript } from '../../initializers/database' | 16 | import { sequelizeTypescript } from '../../initializers/database' |
19 | import { createPlaylistMiniatureFromUrl } from '../thumbnail' | 17 | import { createPlaylistMiniatureFromUrl } from '../thumbnail' |
20 | import { FilteredModelAttributes } from '../../typings/sequelize' | 18 | import { FilteredModelAttributes } from '../../typings/sequelize' |
21 | import { AccountModelId } from '../../typings/models' | 19 | import { MAccountDefault, MAccountId, MVideoId } from '../../typings/models' |
20 | import { MVideoPlaylist, MVideoPlaylistId, MVideoPlaylistOwner } from '../../typings/models/video/video-playlist' | ||
22 | 21 | ||
23 | function playlistObjectToDBAttributes (playlistObject: PlaylistObject, byAccount: AccountModelId, to: string[]) { | 22 | function playlistObjectToDBAttributes (playlistObject: PlaylistObject, byAccount: MAccountId, to: string[]) { |
24 | const privacy = to.indexOf(ACTIVITY_PUB.PUBLIC) !== -1 ? VideoPlaylistPrivacy.PUBLIC : VideoPlaylistPrivacy.UNLISTED | 23 | const privacy = to.indexOf(ACTIVITY_PUB.PUBLIC) !== -1 ? VideoPlaylistPrivacy.PUBLIC : VideoPlaylistPrivacy.UNLISTED |
25 | 24 | ||
26 | return { | 25 | return { |
@@ -36,7 +35,7 @@ function playlistObjectToDBAttributes (playlistObject: PlaylistObject, byAccount | |||
36 | } | 35 | } |
37 | } | 36 | } |
38 | 37 | ||
39 | function playlistElementObjectToDBAttributes (elementObject: PlaylistElementObject, videoPlaylist: VideoPlaylistModel, video: VideoModel) { | 38 | function playlistElementObjectToDBAttributes (elementObject: PlaylistElementObject, videoPlaylist: MVideoPlaylistId, video: MVideoId) { |
40 | return { | 39 | return { |
41 | position: elementObject.position, | 40 | position: elementObject.position, |
42 | url: elementObject.id, | 41 | url: elementObject.id, |
@@ -47,7 +46,7 @@ function playlistElementObjectToDBAttributes (elementObject: PlaylistElementObje | |||
47 | } | 46 | } |
48 | } | 47 | } |
49 | 48 | ||
50 | async function createAccountPlaylists (playlistUrls: string[], account: AccountModel) { | 49 | async function createAccountPlaylists (playlistUrls: string[], account: MAccountDefault) { |
51 | await Bluebird.map(playlistUrls, async playlistUrl => { | 50 | await Bluebird.map(playlistUrls, async playlistUrl => { |
52 | try { | 51 | try { |
53 | const exists = await VideoPlaylistModel.doesPlaylistExist(playlistUrl) | 52 | const exists = await VideoPlaylistModel.doesPlaylistExist(playlistUrl) |
@@ -75,7 +74,7 @@ async function createAccountPlaylists (playlistUrls: string[], account: AccountM | |||
75 | }, { concurrency: CRAWL_REQUEST_CONCURRENCY }) | 74 | }, { concurrency: CRAWL_REQUEST_CONCURRENCY }) |
76 | } | 75 | } |
77 | 76 | ||
78 | async function createOrUpdateVideoPlaylist (playlistObject: PlaylistObject, byAccount: AccountModelId, to: string[]) { | 77 | async function createOrUpdateVideoPlaylist (playlistObject: PlaylistObject, byAccount: MAccountId, to: string[]) { |
79 | const playlistAttributes = playlistObjectToDBAttributes(playlistObject, byAccount, to) | 78 | const playlistAttributes = playlistObjectToDBAttributes(playlistObject, byAccount, to) |
80 | 79 | ||
81 | if (isArray(playlistObject.attributedTo) && playlistObject.attributedTo.length === 1) { | 80 | if (isArray(playlistObject.attributedTo) && playlistObject.attributedTo.length === 1) { |
@@ -88,7 +87,7 @@ async function createOrUpdateVideoPlaylist (playlistObject: PlaylistObject, byAc | |||
88 | } | 87 | } |
89 | } | 88 | } |
90 | 89 | ||
91 | const [ playlist ] = await VideoPlaylistModel.upsert<VideoPlaylistModel>(playlistAttributes, { returning: true }) | 90 | const [ playlist ] = await VideoPlaylistModel.upsert<MVideoPlaylist>(playlistAttributes, { returning: true }) |
92 | 91 | ||
93 | let accItems: string[] = [] | 92 | let accItems: string[] = [] |
94 | await crawlCollectionPage<string>(playlistObject.id, items => { | 93 | await crawlCollectionPage<string>(playlistObject.id, items => { |
@@ -114,7 +113,7 @@ async function createOrUpdateVideoPlaylist (playlistObject: PlaylistObject, byAc | |||
114 | return resetVideoPlaylistElements(accItems, refreshedPlaylist) | 113 | return resetVideoPlaylistElements(accItems, refreshedPlaylist) |
115 | } | 114 | } |
116 | 115 | ||
117 | async function refreshVideoPlaylistIfNeeded (videoPlaylist: VideoPlaylistModel): Promise<VideoPlaylistModel> { | 116 | async function refreshVideoPlaylistIfNeeded (videoPlaylist: MVideoPlaylistOwner): Promise<MVideoPlaylistOwner> { |
118 | if (!videoPlaylist.isOutdated()) return videoPlaylist | 117 | if (!videoPlaylist.isOutdated()) return videoPlaylist |
119 | 118 | ||
120 | try { | 119 | try { |
@@ -157,7 +156,7 @@ export { | |||
157 | 156 | ||
158 | // --------------------------------------------------------------------------- | 157 | // --------------------------------------------------------------------------- |
159 | 158 | ||
160 | async function resetVideoPlaylistElements (elementUrls: string[], playlist: VideoPlaylistModel) { | 159 | async function resetVideoPlaylistElements (elementUrls: string[], playlist: MVideoPlaylist) { |
161 | const elementsToCreate: FilteredModelAttributes<VideoPlaylistElementModel>[] = [] | 160 | const elementsToCreate: FilteredModelAttributes<VideoPlaylistElementModel>[] = [] |
162 | 161 | ||
163 | await Bluebird.map(elementUrls, async elementUrl => { | 162 | await Bluebird.map(elementUrls, async elementUrl => { |
diff --git a/server/lib/activitypub/process/process-accept.ts b/server/lib/activitypub/process/process-accept.ts index cf27e6c32..dcfbb2c84 100644 --- a/server/lib/activitypub/process/process-accept.ts +++ b/server/lib/activitypub/process/process-accept.ts | |||
@@ -1,9 +1,8 @@ | |||
1 | import { ActivityAccept } from '../../../../shared/models/activitypub' | 1 | import { ActivityAccept } from '../../../../shared/models/activitypub' |
2 | import { ActorModel } from '../../../models/activitypub/actor' | ||
3 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' | 2 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' |
4 | import { addFetchOutboxJob } from '../actor' | 3 | import { addFetchOutboxJob } from '../actor' |
5 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' | 4 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' |
6 | import { SignatureActorModel } from '../../../typings/models' | 5 | import { MActorDefault, MActorSignature } from '../../../typings/models' |
7 | 6 | ||
8 | async function processAcceptActivity (options: APProcessorOptions<ActivityAccept>) { | 7 | async function processAcceptActivity (options: APProcessorOptions<ActivityAccept>) { |
9 | const { byActor: targetActor, inboxActor } = options | 8 | const { byActor: targetActor, inboxActor } = options |
@@ -20,12 +19,12 @@ export { | |||
20 | 19 | ||
21 | // --------------------------------------------------------------------------- | 20 | // --------------------------------------------------------------------------- |
22 | 21 | ||
23 | async function processAccept (actor: ActorModel, targetActor: SignatureActorModel) { | 22 | async function processAccept (actor: MActorDefault, targetActor: MActorSignature) { |
24 | const follow = await ActorFollowModel.loadByActorAndTarget(actor.id, targetActor.id) | 23 | const follow = await ActorFollowModel.loadByActorAndTarget(actor.id, targetActor.id) |
25 | if (!follow) throw new Error('Cannot find associated follow.') | 24 | if (!follow) throw new Error('Cannot find associated follow.') |
26 | 25 | ||
27 | if (follow.state !== 'accepted') { | 26 | if (follow.state !== 'accepted') { |
28 | follow.set('state', 'accepted') | 27 | follow.state = 'accepted' |
29 | await follow.save() | 28 | await follow.save() |
30 | 29 | ||
31 | await addFetchOutboxJob(targetActor) | 30 | await addFetchOutboxJob(targetActor) |
diff --git a/server/lib/activitypub/process/process-announce.ts b/server/lib/activitypub/process/process-announce.ts index b3cdc4441..7e22125d5 100644 --- a/server/lib/activitypub/process/process-announce.ts +++ b/server/lib/activitypub/process/process-announce.ts | |||
@@ -5,10 +5,9 @@ import { VideoShareModel } from '../../../models/video/video-share' | |||
5 | import { forwardVideoRelatedActivity } from '../send/utils' | 5 | import { forwardVideoRelatedActivity } from '../send/utils' |
6 | import { getOrCreateVideoAndAccountAndChannel } from '../videos' | 6 | import { getOrCreateVideoAndAccountAndChannel } from '../videos' |
7 | import { Notifier } from '../../notifier' | 7 | import { Notifier } from '../../notifier' |
8 | import { VideoModel } from '../../../models/video/video' | ||
9 | import { logger } from '../../../helpers/logger' | 8 | import { logger } from '../../../helpers/logger' |
10 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' | 9 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' |
11 | import { SignatureActorModel } from '../../../typings/models' | 10 | import { MActorSignature, MVideoAccountLightBlacklistAllFiles } from '../../../typings/models' |
12 | 11 | ||
13 | async function processAnnounceActivity (options: APProcessorOptions<ActivityAnnounce>) { | 12 | async function processAnnounceActivity (options: APProcessorOptions<ActivityAnnounce>) { |
14 | const { activity, byActor: actorAnnouncer } = options | 13 | const { activity, byActor: actorAnnouncer } = options |
@@ -26,10 +25,10 @@ export { | |||
26 | 25 | ||
27 | // --------------------------------------------------------------------------- | 26 | // --------------------------------------------------------------------------- |
28 | 27 | ||
29 | async function processVideoShare (actorAnnouncer: SignatureActorModel, activity: ActivityAnnounce, notify: boolean) { | 28 | async function processVideoShare (actorAnnouncer: MActorSignature, activity: ActivityAnnounce, notify: boolean) { |
30 | const objectUri = typeof activity.object === 'string' ? activity.object : activity.object.id | 29 | const objectUri = typeof activity.object === 'string' ? activity.object : activity.object.id |
31 | 30 | ||
32 | let video: VideoModel | 31 | let video: MVideoAccountLightBlacklistAllFiles |
33 | let videoCreated: boolean | 32 | let videoCreated: boolean |
34 | 33 | ||
35 | try { | 34 | try { |
diff --git a/server/lib/activitypub/process/process-create.ts b/server/lib/activitypub/process/process-create.ts index 6815c6997..bee853721 100644 --- a/server/lib/activitypub/process/process-create.ts +++ b/server/lib/activitypub/process/process-create.ts | |||
@@ -10,10 +10,8 @@ import { createOrUpdateCacheFile } from '../cache-file' | |||
10 | import { Notifier } from '../../notifier' | 10 | import { Notifier } from '../../notifier' |
11 | import { PlaylistObject } from '../../../../shared/models/activitypub/objects/playlist-object' | 11 | import { PlaylistObject } from '../../../../shared/models/activitypub/objects/playlist-object' |
12 | import { createOrUpdateVideoPlaylist } from '../playlist' | 12 | import { createOrUpdateVideoPlaylist } from '../playlist' |
13 | import { VideoModel } from '../../../models/video/video' | ||
14 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' | 13 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' |
15 | import { VideoCommentModel } from '../../../models/video/video-comment' | 14 | import { MActorSignature, MCommentOwnerVideo, MVideoAccountLightBlacklistAllFiles } from '../../../typings/models' |
16 | import { SignatureActorModel } from '../../../typings/models' | ||
17 | 15 | ||
18 | async function processCreateActivity (options: APProcessorOptions<ActivityCreate>) { | 16 | async function processCreateActivity (options: APProcessorOptions<ActivityCreate>) { |
19 | const { activity, byActor } = options | 17 | const { activity, byActor } = options |
@@ -61,7 +59,7 @@ async function processCreateVideo (activity: ActivityCreate, notify: boolean) { | |||
61 | return video | 59 | return video |
62 | } | 60 | } |
63 | 61 | ||
64 | async function processCreateCacheFile (activity: ActivityCreate, byActor: SignatureActorModel) { | 62 | async function processCreateCacheFile (activity: ActivityCreate, byActor: MActorSignature) { |
65 | const cacheFile = activity.object as CacheFileObject | 63 | const cacheFile = activity.object as CacheFileObject |
66 | 64 | ||
67 | const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: cacheFile.object }) | 65 | const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: cacheFile.object }) |
@@ -77,15 +75,15 @@ async function processCreateCacheFile (activity: ActivityCreate, byActor: Signat | |||
77 | } | 75 | } |
78 | } | 76 | } |
79 | 77 | ||
80 | async function processCreateVideoComment (activity: ActivityCreate, byActor: SignatureActorModel, notify: boolean) { | 78 | async function processCreateVideoComment (activity: ActivityCreate, byActor: MActorSignature, notify: boolean) { |
81 | const commentObject = activity.object as VideoCommentObject | 79 | const commentObject = activity.object as VideoCommentObject |
82 | const byAccount = byActor.Account | 80 | const byAccount = byActor.Account |
83 | 81 | ||
84 | if (!byAccount) throw new Error('Cannot create video comment with the non account actor ' + byActor.url) | 82 | if (!byAccount) throw new Error('Cannot create video comment with the non account actor ' + byActor.url) |
85 | 83 | ||
86 | let video: VideoModel | 84 | let video: MVideoAccountLightBlacklistAllFiles |
87 | let created: boolean | 85 | let created: boolean |
88 | let comment: VideoCommentModel | 86 | let comment: MCommentOwnerVideo |
89 | try { | 87 | try { |
90 | const resolveThreadResult = await resolveThread({ url: commentObject.id, isVideo: false }) | 88 | const resolveThreadResult = await resolveThread({ url: commentObject.id, isVideo: false }) |
91 | video = resolveThreadResult.video | 89 | video = resolveThreadResult.video |
@@ -110,7 +108,7 @@ async function processCreateVideoComment (activity: ActivityCreate, byActor: Sig | |||
110 | if (created && notify) Notifier.Instance.notifyOnNewComment(comment) | 108 | if (created && notify) Notifier.Instance.notifyOnNewComment(comment) |
111 | } | 109 | } |
112 | 110 | ||
113 | async function processCreatePlaylist (activity: ActivityCreate, byActor: SignatureActorModel) { | 111 | async function processCreatePlaylist (activity: ActivityCreate, byActor: MActorSignature) { |
114 | const playlistObject = activity.object as PlaylistObject | 112 | const playlistObject = activity.object as PlaylistObject |
115 | const byAccount = byActor.Account | 113 | const byAccount = byActor.Account |
116 | 114 | ||
diff --git a/server/lib/activitypub/process/process-delete.ts b/server/lib/activitypub/process/process-delete.ts index 344d14322..79d0e0d79 100644 --- a/server/lib/activitypub/process/process-delete.ts +++ b/server/lib/activitypub/process/process-delete.ts | |||
@@ -2,15 +2,13 @@ import { ActivityDelete } from '../../../../shared/models/activitypub' | |||
2 | import { retryTransactionWrapper } from '../../../helpers/database-utils' | 2 | import { retryTransactionWrapper } from '../../../helpers/database-utils' |
3 | import { logger } from '../../../helpers/logger' | 3 | import { logger } from '../../../helpers/logger' |
4 | import { sequelizeTypescript } from '../../../initializers' | 4 | import { sequelizeTypescript } from '../../../initializers' |
5 | import { AccountModel } from '../../../models/account/account' | ||
6 | import { ActorModel } from '../../../models/activitypub/actor' | 5 | import { ActorModel } from '../../../models/activitypub/actor' |
7 | import { VideoModel } from '../../../models/video/video' | 6 | import { VideoModel } from '../../../models/video/video' |
8 | import { VideoChannelModel } from '../../../models/video/video-channel' | ||
9 | import { VideoCommentModel } from '../../../models/video/video-comment' | 7 | import { VideoCommentModel } from '../../../models/video/video-comment' |
10 | import { forwardVideoRelatedActivity } from '../send/utils' | 8 | import { forwardVideoRelatedActivity } from '../send/utils' |
11 | import { VideoPlaylistModel } from '../../../models/video/video-playlist' | 9 | import { VideoPlaylistModel } from '../../../models/video/video-playlist' |
12 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' | 10 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' |
13 | import { SignatureActorModel } from '../../../typings/models' | 11 | import { MAccountActor, MActor, MActorSignature, MChannelActor, MChannelActorAccountActor } from '../../../typings/models' |
14 | 12 | ||
15 | async function processDeleteActivity (options: APProcessorOptions<ActivityDelete>) { | 13 | async function processDeleteActivity (options: APProcessorOptions<ActivityDelete>) { |
16 | const { activity, byActor } = options | 14 | const { activity, byActor } = options |
@@ -24,13 +22,17 @@ async function processDeleteActivity (options: APProcessorOptions<ActivityDelete | |||
24 | if (byActorFull.type === 'Person') { | 22 | if (byActorFull.type === 'Person') { |
25 | if (!byActorFull.Account) throw new Error('Actor ' + byActorFull.url + ' is a person but we cannot find it in database.') | 23 | if (!byActorFull.Account) throw new Error('Actor ' + byActorFull.url + ' is a person but we cannot find it in database.') |
26 | 24 | ||
27 | byActorFull.Account.Actor = await byActorFull.Account.$get('Actor') as ActorModel | 25 | const accountToDelete = byActorFull.Account as MAccountActor |
28 | return retryTransactionWrapper(processDeleteAccount, byActorFull.Account) | 26 | accountToDelete.Actor = byActorFull |
27 | |||
28 | return retryTransactionWrapper(processDeleteAccount, accountToDelete) | ||
29 | } else if (byActorFull.type === 'Group') { | 29 | } else if (byActorFull.type === 'Group') { |
30 | if (!byActorFull.VideoChannel) throw new Error('Actor ' + byActorFull.url + ' is a group but we cannot find it in database.') | 30 | if (!byActorFull.VideoChannel) throw new Error('Actor ' + byActorFull.url + ' is a group but we cannot find it in database.') |
31 | 31 | ||
32 | byActorFull.VideoChannel.Actor = await byActorFull.VideoChannel.$get('Actor') as ActorModel | 32 | const channelToDelete = byActorFull.VideoChannel as MChannelActorAccountActor |
33 | return retryTransactionWrapper(processDeleteVideoChannel, byActorFull.VideoChannel) | 33 | channelToDelete.Actor = byActorFull |
34 | |||
35 | return retryTransactionWrapper(processDeleteVideoChannel, channelToDelete) | ||
34 | } | 36 | } |
35 | } | 37 | } |
36 | 38 | ||
@@ -70,7 +72,7 @@ export { | |||
70 | 72 | ||
71 | // --------------------------------------------------------------------------- | 73 | // --------------------------------------------------------------------------- |
72 | 74 | ||
73 | async function processDeleteVideo (actor: ActorModel, videoToDelete: VideoModel) { | 75 | async function processDeleteVideo (actor: MActor, videoToDelete: VideoModel) { |
74 | logger.debug('Removing remote video "%s".', videoToDelete.uuid) | 76 | logger.debug('Removing remote video "%s".', videoToDelete.uuid) |
75 | 77 | ||
76 | await sequelizeTypescript.transaction(async t => { | 78 | await sequelizeTypescript.transaction(async t => { |
@@ -84,7 +86,7 @@ async function processDeleteVideo (actor: ActorModel, videoToDelete: VideoModel) | |||
84 | logger.info('Remote video with uuid %s removed.', videoToDelete.uuid) | 86 | logger.info('Remote video with uuid %s removed.', videoToDelete.uuid) |
85 | } | 87 | } |
86 | 88 | ||
87 | async function processDeleteVideoPlaylist (actor: ActorModel, playlistToDelete: VideoPlaylistModel) { | 89 | async function processDeleteVideoPlaylist (actor: MActor, playlistToDelete: VideoPlaylistModel) { |
88 | logger.debug('Removing remote video playlist "%s".', playlistToDelete.uuid) | 90 | logger.debug('Removing remote video playlist "%s".', playlistToDelete.uuid) |
89 | 91 | ||
90 | await sequelizeTypescript.transaction(async t => { | 92 | await sequelizeTypescript.transaction(async t => { |
@@ -98,7 +100,7 @@ async function processDeleteVideoPlaylist (actor: ActorModel, playlistToDelete: | |||
98 | logger.info('Remote video playlist with uuid %s removed.', playlistToDelete.uuid) | 100 | logger.info('Remote video playlist with uuid %s removed.', playlistToDelete.uuid) |
99 | } | 101 | } |
100 | 102 | ||
101 | async function processDeleteAccount (accountToRemove: AccountModel) { | 103 | async function processDeleteAccount (accountToRemove: MAccountActor) { |
102 | logger.debug('Removing remote account "%s".', accountToRemove.Actor.url) | 104 | logger.debug('Removing remote account "%s".', accountToRemove.Actor.url) |
103 | 105 | ||
104 | await sequelizeTypescript.transaction(async t => { | 106 | await sequelizeTypescript.transaction(async t => { |
@@ -108,7 +110,7 @@ async function processDeleteAccount (accountToRemove: AccountModel) { | |||
108 | logger.info('Remote account %s removed.', accountToRemove.Actor.url) | 110 | logger.info('Remote account %s removed.', accountToRemove.Actor.url) |
109 | } | 111 | } |
110 | 112 | ||
111 | async function processDeleteVideoChannel (videoChannelToRemove: VideoChannelModel) { | 113 | async function processDeleteVideoChannel (videoChannelToRemove: MChannelActor) { |
112 | logger.debug('Removing remote video channel "%s".', videoChannelToRemove.Actor.url) | 114 | logger.debug('Removing remote video channel "%s".', videoChannelToRemove.Actor.url) |
113 | 115 | ||
114 | await sequelizeTypescript.transaction(async t => { | 116 | await sequelizeTypescript.transaction(async t => { |
@@ -118,7 +120,7 @@ async function processDeleteVideoChannel (videoChannelToRemove: VideoChannelMode | |||
118 | logger.info('Remote video channel %s removed.', videoChannelToRemove.Actor.url) | 120 | logger.info('Remote video channel %s removed.', videoChannelToRemove.Actor.url) |
119 | } | 121 | } |
120 | 122 | ||
121 | function processDeleteVideoComment (byActor: SignatureActorModel, videoComment: VideoCommentModel, activity: ActivityDelete) { | 123 | function processDeleteVideoComment (byActor: MActorSignature, videoComment: VideoCommentModel, activity: ActivityDelete) { |
122 | logger.debug('Removing remote video comment "%s".', videoComment.url) | 124 | logger.debug('Removing remote video comment "%s".', videoComment.url) |
123 | 125 | ||
124 | return sequelizeTypescript.transaction(async t => { | 126 | return sequelizeTypescript.transaction(async t => { |
diff --git a/server/lib/activitypub/process/process-dislike.ts b/server/lib/activitypub/process/process-dislike.ts index 727fcfee0..debd8a67c 100644 --- a/server/lib/activitypub/process/process-dislike.ts +++ b/server/lib/activitypub/process/process-dislike.ts | |||
@@ -7,7 +7,7 @@ import { getOrCreateVideoAndAccountAndChannel } from '../videos' | |||
7 | import { forwardVideoRelatedActivity } from '../send/utils' | 7 | import { forwardVideoRelatedActivity } from '../send/utils' |
8 | import { getVideoDislikeActivityPubUrl } from '../url' | 8 | import { getVideoDislikeActivityPubUrl } from '../url' |
9 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' | 9 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' |
10 | import { SignatureActorModel } from '../../../typings/models' | 10 | import { MActorSignature } from '../../../typings/models' |
11 | 11 | ||
12 | async function processDislikeActivity (options: APProcessorOptions<ActivityCreate | ActivityDislike>) { | 12 | async function processDislikeActivity (options: APProcessorOptions<ActivityCreate | ActivityDislike>) { |
13 | const { activity, byActor } = options | 13 | const { activity, byActor } = options |
@@ -22,7 +22,7 @@ export { | |||
22 | 22 | ||
23 | // --------------------------------------------------------------------------- | 23 | // --------------------------------------------------------------------------- |
24 | 24 | ||
25 | async function processDislike (activity: ActivityCreate | ActivityDislike, byActor: SignatureActorModel) { | 25 | async function processDislike (activity: ActivityCreate | ActivityDislike, byActor: MActorSignature) { |
26 | const dislikeObject = activity.type === 'Dislike' ? activity.object : (activity.object as DislikeObject).object | 26 | const dislikeObject = activity.type === 'Dislike' ? activity.object : (activity.object as DislikeObject).object |
27 | const byAccount = byActor.Account | 27 | const byAccount = byActor.Account |
28 | 28 | ||
diff --git a/server/lib/activitypub/process/process-flag.ts b/server/lib/activitypub/process/process-flag.ts index 1f8a80c14..e6e9084de 100644 --- a/server/lib/activitypub/process/process-flag.ts +++ b/server/lib/activitypub/process/process-flag.ts | |||
@@ -8,7 +8,7 @@ import { getOrCreateVideoAndAccountAndChannel } from '../videos' | |||
8 | import { Notifier } from '../../notifier' | 8 | import { Notifier } from '../../notifier' |
9 | import { getAPId } from '../../../helpers/activitypub' | 9 | import { getAPId } from '../../../helpers/activitypub' |
10 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' | 10 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' |
11 | import { SignatureActorModel } from '../../../typings/models' | 11 | import { MActorSignature, MVideoAbuseVideo } from '../../../typings/models' |
12 | 12 | ||
13 | async function processFlagActivity (options: APProcessorOptions<ActivityCreate | ActivityFlag>) { | 13 | async function processFlagActivity (options: APProcessorOptions<ActivityCreate | ActivityFlag>) { |
14 | const { activity, byActor } = options | 14 | const { activity, byActor } = options |
@@ -23,31 +23,39 @@ export { | |||
23 | 23 | ||
24 | // --------------------------------------------------------------------------- | 24 | // --------------------------------------------------------------------------- |
25 | 25 | ||
26 | async function processCreateVideoAbuse (activity: ActivityCreate | ActivityFlag, byActor: SignatureActorModel) { | 26 | async function processCreateVideoAbuse (activity: ActivityCreate | ActivityFlag, byActor: MActorSignature) { |
27 | const flag = activity.type === 'Flag' ? activity : (activity.object as VideoAbuseObject) | 27 | const flag = activity.type === 'Flag' ? activity : (activity.object as VideoAbuseObject) |
28 | 28 | ||
29 | logger.debug('Reporting remote abuse for video %s.', getAPId(flag.object)) | ||
30 | |||
31 | const account = byActor.Account | 29 | const account = byActor.Account |
32 | if (!account) throw new Error('Cannot create video abuse with the non account actor ' + byActor.url) | 30 | if (!account) throw new Error('Cannot create video abuse with the non account actor ' + byActor.url) |
33 | 31 | ||
34 | const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: flag.object }) | 32 | const objects = Array.isArray(flag.object) ? flag.object : [ flag.object ] |
35 | 33 | ||
36 | const videoAbuse = await sequelizeTypescript.transaction(async t => { | 34 | for (const object of objects) { |
37 | const videoAbuseData = { | 35 | try { |
38 | reporterAccountId: account.id, | 36 | logger.debug('Reporting remote abuse for video %s.', getAPId(object)) |
39 | reason: flag.content, | 37 | |
40 | videoId: video.id, | 38 | const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: object }) |
41 | state: VideoAbuseState.PENDING | ||
42 | } | ||
43 | 39 | ||
44 | const videoAbuseInstance = await VideoAbuseModel.create(videoAbuseData, { transaction: t }) | 40 | const videoAbuse = await sequelizeTypescript.transaction(async t => { |
45 | videoAbuseInstance.Video = video | 41 | const videoAbuseData = { |
42 | reporterAccountId: account.id, | ||
43 | reason: flag.content, | ||
44 | videoId: video.id, | ||
45 | state: VideoAbuseState.PENDING | ||
46 | } | ||
46 | 47 | ||
47 | logger.info('Remote abuse for video uuid %s created', flag.object) | 48 | const videoAbuseInstance = await VideoAbuseModel.create(videoAbuseData, { transaction: t }) as MVideoAbuseVideo |
49 | videoAbuseInstance.Video = video | ||
48 | 50 | ||
49 | return videoAbuseInstance | 51 | logger.info('Remote abuse for video uuid %s created', flag.object) |
50 | }) | ||
51 | 52 | ||
52 | Notifier.Instance.notifyOnNewVideoAbuse(videoAbuse) | 53 | return videoAbuseInstance |
54 | }) | ||
55 | |||
56 | Notifier.Instance.notifyOnNewVideoAbuse(videoAbuse) | ||
57 | } catch (err) { | ||
58 | logger.debug('Cannot process report of %s. (Maybe not a video abuse).', getAPId(object), { err }) | ||
59 | } | ||
60 | } | ||
53 | } | 61 | } |
diff --git a/server/lib/activitypub/process/process-follow.ts b/server/lib/activitypub/process/process-follow.ts index 240aa5799..85f22d654 100644 --- a/server/lib/activitypub/process/process-follow.ts +++ b/server/lib/activitypub/process/process-follow.ts | |||
@@ -10,8 +10,8 @@ import { getAPId } from '../../../helpers/activitypub' | |||
10 | import { getServerActor } from '../../../helpers/utils' | 10 | import { getServerActor } from '../../../helpers/utils' |
11 | import { CONFIG } from '../../../initializers/config' | 11 | import { CONFIG } from '../../../initializers/config' |
12 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' | 12 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' |
13 | import { SignatureActorModel } from '../../../typings/models' | 13 | import { MActorFollowActors, MActorSignature } from '../../../typings/models' |
14 | import { ActorFollowModelLight } from '../../../typings/models/actor-follow' | 14 | import { autoFollowBackIfNeeded } from '../follow' |
15 | 15 | ||
16 | async function processFollowActivity (options: APProcessorOptions<ActivityFollow>) { | 16 | async function processFollowActivity (options: APProcessorOptions<ActivityFollow>) { |
17 | const { activity, byActor } = options | 17 | const { activity, byActor } = options |
@@ -28,8 +28,8 @@ export { | |||
28 | 28 | ||
29 | // --------------------------------------------------------------------------- | 29 | // --------------------------------------------------------------------------- |
30 | 30 | ||
31 | async function processFollow (byActor: SignatureActorModel, targetActorURL: string) { | 31 | async function processFollow (byActor: MActorSignature, targetActorURL: string) { |
32 | const { actorFollow, created, isFollowingInstance } = await sequelizeTypescript.transaction(async t => { | 32 | const { actorFollow, created, isFollowingInstance, targetActor } = await sequelizeTypescript.transaction(async t => { |
33 | const targetActor = await ActorModel.loadByUrlAndPopulateAccountAndChannel(targetActorURL, t) | 33 | const targetActor = await ActorModel.loadByUrlAndPopulateAccountAndChannel(targetActorURL, t) |
34 | 34 | ||
35 | if (!targetActor) throw new Error('Unknown actor') | 35 | if (!targetActor) throw new Error('Unknown actor') |
@@ -43,10 +43,10 @@ async function processFollow (byActor: SignatureActorModel, targetActorURL: stri | |||
43 | 43 | ||
44 | await sendReject(byActor, targetActor) | 44 | await sendReject(byActor, targetActor) |
45 | 45 | ||
46 | return { actorFollow: undefined } | 46 | return { actorFollow: undefined as MActorFollowActors } |
47 | } | 47 | } |
48 | 48 | ||
49 | const [ actorFollow, created ] = await ActorFollowModel.findOrCreate({ | 49 | const [ actorFollow, created ] = await ActorFollowModel.findOrCreate<MActorFollowActors>({ |
50 | where: { | 50 | where: { |
51 | actorId: byActor.id, | 51 | actorId: byActor.id, |
52 | targetActorId: targetActor.id | 52 | targetActorId: targetActor.id |
@@ -57,7 +57,7 @@ async function processFollow (byActor: SignatureActorModel, targetActorURL: stri | |||
57 | state: CONFIG.FOLLOWERS.INSTANCE.MANUAL_APPROVAL ? 'pending' : 'accepted' | 57 | state: CONFIG.FOLLOWERS.INSTANCE.MANUAL_APPROVAL ? 'pending' : 'accepted' |
58 | }, | 58 | }, |
59 | transaction: t | 59 | transaction: t |
60 | }) as [ ActorFollowModelLight, boolean ] | 60 | }) |
61 | 61 | ||
62 | if (actorFollow.state !== 'accepted' && CONFIG.FOLLOWERS.INSTANCE.MANUAL_APPROVAL === false) { | 62 | if (actorFollow.state !== 'accepted' && CONFIG.FOLLOWERS.INSTANCE.MANUAL_APPROVAL === false) { |
63 | actorFollow.state = 'accepted' | 63 | actorFollow.state = 'accepted' |
@@ -68,17 +68,26 @@ async function processFollow (byActor: SignatureActorModel, targetActorURL: stri | |||
68 | actorFollow.ActorFollowing = targetActor | 68 | actorFollow.ActorFollowing = targetActor |
69 | 69 | ||
70 | // Target sends to actor he accepted the follow request | 70 | // Target sends to actor he accepted the follow request |
71 | if (actorFollow.state === 'accepted') await sendAccept(actorFollow) | 71 | if (actorFollow.state === 'accepted') { |
72 | await sendAccept(actorFollow) | ||
73 | await autoFollowBackIfNeeded(actorFollow) | ||
74 | } | ||
72 | 75 | ||
73 | return { actorFollow, created, isFollowingInstance } | 76 | return { actorFollow, created, isFollowingInstance, targetActor } |
74 | }) | 77 | }) |
75 | 78 | ||
76 | // Rejected | 79 | // Rejected |
77 | if (!actorFollow) return | 80 | if (!actorFollow) return |
78 | 81 | ||
79 | if (created) { | 82 | if (created) { |
80 | if (isFollowingInstance) Notifier.Instance.notifyOfNewInstanceFollow(actorFollow) | 83 | const follower = await ActorModel.loadFull(byActor.id) |
81 | else Notifier.Instance.notifyOfNewUserFollow(actorFollow) | 84 | const actorFollowFull = Object.assign(actorFollow, { ActorFollowing: targetActor, ActorFollower: follower }) |
85 | |||
86 | if (isFollowingInstance) { | ||
87 | Notifier.Instance.notifyOfNewInstanceFollow(actorFollowFull) | ||
88 | } else { | ||
89 | Notifier.Instance.notifyOfNewUserFollow(actorFollowFull) | ||
90 | } | ||
82 | } | 91 | } |
83 | 92 | ||
84 | logger.info('Actor %s is followed by actor %s.', targetActorURL, byActor.url) | 93 | logger.info('Actor %s is followed by actor %s.', targetActorURL, byActor.url) |
diff --git a/server/lib/activitypub/process/process-like.ts b/server/lib/activitypub/process/process-like.ts index cf559af72..62be0de42 100644 --- a/server/lib/activitypub/process/process-like.ts +++ b/server/lib/activitypub/process/process-like.ts | |||
@@ -7,7 +7,7 @@ import { getOrCreateVideoAndAccountAndChannel } from '../videos' | |||
7 | import { getVideoLikeActivityPubUrl } from '../url' | 7 | import { getVideoLikeActivityPubUrl } from '../url' |
8 | import { getAPId } from '../../../helpers/activitypub' | 8 | import { getAPId } from '../../../helpers/activitypub' |
9 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' | 9 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' |
10 | import { SignatureActorModel } from '../../../typings/models' | 10 | import { MActorSignature } from '../../../typings/models' |
11 | 11 | ||
12 | async function processLikeActivity (options: APProcessorOptions<ActivityLike>) { | 12 | async function processLikeActivity (options: APProcessorOptions<ActivityLike>) { |
13 | const { activity, byActor } = options | 13 | const { activity, byActor } = options |
@@ -22,7 +22,7 @@ export { | |||
22 | 22 | ||
23 | // --------------------------------------------------------------------------- | 23 | // --------------------------------------------------------------------------- |
24 | 24 | ||
25 | async function processLikeVideo (byActor: SignatureActorModel, activity: ActivityLike) { | 25 | async function processLikeVideo (byActor: MActorSignature, activity: ActivityLike) { |
26 | const videoUrl = getAPId(activity.object) | 26 | const videoUrl = getAPId(activity.object) |
27 | 27 | ||
28 | const byAccount = byActor.Account | 28 | const byAccount = byActor.Account |
diff --git a/server/lib/activitypub/process/process-reject.ts b/server/lib/activitypub/process/process-reject.ts index 22e311ceb..00e9afa10 100644 --- a/server/lib/activitypub/process/process-reject.ts +++ b/server/lib/activitypub/process/process-reject.ts | |||
@@ -2,7 +2,7 @@ import { ActivityReject } from '../../../../shared/models/activitypub/activity' | |||
2 | import { sequelizeTypescript } from '../../../initializers' | 2 | import { sequelizeTypescript } from '../../../initializers' |
3 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' | 3 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' |
4 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' | 4 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' |
5 | import { ActorModelOnly } from '../../../typings/models' | 5 | import { MActor } from '../../../typings/models' |
6 | 6 | ||
7 | async function processRejectActivity (options: APProcessorOptions<ActivityReject>) { | 7 | async function processRejectActivity (options: APProcessorOptions<ActivityReject>) { |
8 | const { byActor: targetActor, inboxActor } = options | 8 | const { byActor: targetActor, inboxActor } = options |
@@ -19,7 +19,7 @@ export { | |||
19 | 19 | ||
20 | // --------------------------------------------------------------------------- | 20 | // --------------------------------------------------------------------------- |
21 | 21 | ||
22 | async function processReject (follower: ActorModelOnly, targetActor: ActorModelOnly) { | 22 | async function processReject (follower: MActor, targetActor: MActor) { |
23 | return sequelizeTypescript.transaction(async t => { | 23 | return sequelizeTypescript.transaction(async t => { |
24 | const actorFollow = await ActorFollowModel.loadByActorAndTarget(follower.id, targetActor.id, t) | 24 | const actorFollow = await ActorFollowModel.loadByActorAndTarget(follower.id, targetActor.id, t) |
25 | 25 | ||
diff --git a/server/lib/activitypub/process/process-undo.ts b/server/lib/activitypub/process/process-undo.ts index c37ee38bb..10643b2e9 100644 --- a/server/lib/activitypub/process/process-undo.ts +++ b/server/lib/activitypub/process/process-undo.ts | |||
@@ -11,7 +11,7 @@ import { getOrCreateVideoAndAccountAndChannel } from '../videos' | |||
11 | import { VideoShareModel } from '../../../models/video/video-share' | 11 | import { VideoShareModel } from '../../../models/video/video-share' |
12 | import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy' | 12 | import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy' |
13 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' | 13 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' |
14 | import { SignatureActorModel } from '../../../typings/models' | 14 | import { MActorSignature } from '../../../typings/models' |
15 | 15 | ||
16 | async function processUndoActivity (options: APProcessorOptions<ActivityUndo>) { | 16 | async function processUndoActivity (options: APProcessorOptions<ActivityUndo>) { |
17 | const { activity, byActor } = options | 17 | const { activity, byActor } = options |
@@ -54,7 +54,7 @@ export { | |||
54 | 54 | ||
55 | // --------------------------------------------------------------------------- | 55 | // --------------------------------------------------------------------------- |
56 | 56 | ||
57 | async function processUndoLike (byActor: SignatureActorModel, activity: ActivityUndo) { | 57 | async function processUndoLike (byActor: MActorSignature, activity: ActivityUndo) { |
58 | const likeActivity = activity.object as ActivityLike | 58 | const likeActivity = activity.object as ActivityLike |
59 | 59 | ||
60 | const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: likeActivity.object }) | 60 | const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: likeActivity.object }) |
@@ -77,7 +77,7 @@ async function processUndoLike (byActor: SignatureActorModel, activity: Activity | |||
77 | }) | 77 | }) |
78 | } | 78 | } |
79 | 79 | ||
80 | async function processUndoDislike (byActor: SignatureActorModel, activity: ActivityUndo) { | 80 | async function processUndoDislike (byActor: MActorSignature, activity: ActivityUndo) { |
81 | const dislike = activity.object.type === 'Dislike' | 81 | const dislike = activity.object.type === 'Dislike' |
82 | ? activity.object | 82 | ? activity.object |
83 | : activity.object.object as DislikeObject | 83 | : activity.object.object as DislikeObject |
@@ -102,7 +102,7 @@ async function processUndoDislike (byActor: SignatureActorModel, activity: Activ | |||
102 | }) | 102 | }) |
103 | } | 103 | } |
104 | 104 | ||
105 | async function processUndoCacheFile (byActor: SignatureActorModel, activity: ActivityUndo) { | 105 | async function processUndoCacheFile (byActor: MActorSignature, activity: ActivityUndo) { |
106 | const cacheFileObject = activity.object.object as CacheFileObject | 106 | const cacheFileObject = activity.object.object as CacheFileObject |
107 | 107 | ||
108 | const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: cacheFileObject.object }) | 108 | const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: cacheFileObject.object }) |
@@ -127,7 +127,7 @@ async function processUndoCacheFile (byActor: SignatureActorModel, activity: Act | |||
127 | }) | 127 | }) |
128 | } | 128 | } |
129 | 129 | ||
130 | function processUndoFollow (follower: SignatureActorModel, followActivity: ActivityFollow) { | 130 | function processUndoFollow (follower: MActorSignature, followActivity: ActivityFollow) { |
131 | return sequelizeTypescript.transaction(async t => { | 131 | return sequelizeTypescript.transaction(async t => { |
132 | const following = await ActorModel.loadByUrlAndPopulateAccountAndChannel(followActivity.object, t) | 132 | const following = await ActorModel.loadByUrlAndPopulateAccountAndChannel(followActivity.object, t) |
133 | const actorFollow = await ActorFollowModel.loadByActorAndTarget(follower.id, following.id, t) | 133 | const actorFollow = await ActorFollowModel.loadByActorAndTarget(follower.id, following.id, t) |
@@ -140,7 +140,7 @@ function processUndoFollow (follower: SignatureActorModel, followActivity: Activ | |||
140 | }) | 140 | }) |
141 | } | 141 | } |
142 | 142 | ||
143 | function processUndoAnnounce (byActor: SignatureActorModel, announceActivity: ActivityAnnounce) { | 143 | function processUndoAnnounce (byActor: MActorSignature, announceActivity: ActivityAnnounce) { |
144 | return sequelizeTypescript.transaction(async t => { | 144 | return sequelizeTypescript.transaction(async t => { |
145 | const share = await VideoShareModel.loadByUrl(announceActivity.id, t) | 145 | const share = await VideoShareModel.loadByUrl(announceActivity.id, t) |
146 | if (!share) throw new Error(`Unknown video share ${announceActivity.id}.`) | 146 | if (!share) throw new Error(`Unknown video share ${announceActivity.id}.`) |
diff --git a/server/lib/activitypub/process/process-update.ts b/server/lib/activitypub/process/process-update.ts index 414f9e375..a47d605d8 100644 --- a/server/lib/activitypub/process/process-update.ts +++ b/server/lib/activitypub/process/process-update.ts | |||
@@ -15,7 +15,7 @@ import { forwardVideoRelatedActivity } from '../send/utils' | |||
15 | import { PlaylistObject } from '../../../../shared/models/activitypub/objects/playlist-object' | 15 | import { PlaylistObject } from '../../../../shared/models/activitypub/objects/playlist-object' |
16 | import { createOrUpdateVideoPlaylist } from '../playlist' | 16 | import { createOrUpdateVideoPlaylist } from '../playlist' |
17 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' | 17 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' |
18 | import { SignatureActorModel } from '../../../typings/models' | 18 | import { MActorSignature, MAccountIdActor } from '../../../typings/models' |
19 | 19 | ||
20 | async function processUpdateActivity (options: APProcessorOptions<ActivityUpdate>) { | 20 | async function processUpdateActivity (options: APProcessorOptions<ActivityUpdate>) { |
21 | const { activity, byActor } = options | 21 | const { activity, byActor } = options |
@@ -53,7 +53,7 @@ export { | |||
53 | 53 | ||
54 | // --------------------------------------------------------------------------- | 54 | // --------------------------------------------------------------------------- |
55 | 55 | ||
56 | async function processUpdateVideo (actor: SignatureActorModel, activity: ActivityUpdate) { | 56 | async function processUpdateVideo (actor: MActorSignature, activity: ActivityUpdate) { |
57 | const videoObject = activity.object as VideoTorrentObject | 57 | const videoObject = activity.object as VideoTorrentObject |
58 | 58 | ||
59 | if (sanitizeAndCheckVideoTorrentObject(videoObject) === false) { | 59 | if (sanitizeAndCheckVideoTorrentObject(videoObject) === false) { |
@@ -61,20 +61,23 @@ async function processUpdateVideo (actor: SignatureActorModel, activity: Activit | |||
61 | return undefined | 61 | return undefined |
62 | } | 62 | } |
63 | 63 | ||
64 | const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: videoObject.id, allowRefresh: false }) | 64 | const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: videoObject.id, allowRefresh: false, fetchType: 'all' }) |
65 | const channelActor = await getOrCreateVideoChannelFromVideoObject(videoObject) | 65 | const channelActor = await getOrCreateVideoChannelFromVideoObject(videoObject) |
66 | 66 | ||
67 | const account = actor.Account as MAccountIdActor | ||
68 | account.Actor = actor | ||
69 | |||
67 | const updateOptions = { | 70 | const updateOptions = { |
68 | video, | 71 | video, |
69 | videoObject, | 72 | videoObject, |
70 | account: actor.Account, | 73 | account, |
71 | channel: channelActor.VideoChannel, | 74 | channel: channelActor.VideoChannel, |
72 | overrideTo: activity.to | 75 | overrideTo: activity.to |
73 | } | 76 | } |
74 | return updateVideoFromAP(updateOptions) | 77 | return updateVideoFromAP(updateOptions) |
75 | } | 78 | } |
76 | 79 | ||
77 | async function processUpdateCacheFile (byActor: SignatureActorModel, activity: ActivityUpdate) { | 80 | async function processUpdateCacheFile (byActor: MActorSignature, activity: ActivityUpdate) { |
78 | const cacheFileObject = activity.object as CacheFileObject | 81 | const cacheFileObject = activity.object as CacheFileObject |
79 | 82 | ||
80 | if (!isCacheFileObjectValid(cacheFileObject)) { | 83 | if (!isCacheFileObjectValid(cacheFileObject)) { |
@@ -150,7 +153,7 @@ async function processUpdateActor (actor: ActorModel, activity: ActivityUpdate) | |||
150 | } | 153 | } |
151 | } | 154 | } |
152 | 155 | ||
153 | async function processUpdatePlaylist (byActor: SignatureActorModel, activity: ActivityUpdate) { | 156 | async function processUpdatePlaylist (byActor: MActorSignature, activity: ActivityUpdate) { |
154 | const playlistObject = activity.object as PlaylistObject | 157 | const playlistObject = activity.object as PlaylistObject |
155 | const byAccount = byActor.Account | 158 | const byAccount = byActor.Account |
156 | 159 | ||
diff --git a/server/lib/activitypub/process/process-view.ts b/server/lib/activitypub/process/process-view.ts index e4997b828..df29ee968 100644 --- a/server/lib/activitypub/process/process-view.ts +++ b/server/lib/activitypub/process/process-view.ts | |||
@@ -3,7 +3,7 @@ import { forwardVideoRelatedActivity } from '../send/utils' | |||
3 | import { Redis } from '../../redis' | 3 | import { Redis } from '../../redis' |
4 | import { ActivityCreate, ActivityView, ViewObject } from '../../../../shared/models/activitypub' | 4 | import { ActivityCreate, ActivityView, ViewObject } from '../../../../shared/models/activitypub' |
5 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' | 5 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' |
6 | import { SignatureActorModel } from '../../../typings/models' | 6 | import { MActorSignature } from '../../../typings/models' |
7 | 7 | ||
8 | async function processViewActivity (options: APProcessorOptions<ActivityCreate | ActivityView>) { | 8 | async function processViewActivity (options: APProcessorOptions<ActivityCreate | ActivityView>) { |
9 | const { activity, byActor } = options | 9 | const { activity, byActor } = options |
@@ -18,11 +18,11 @@ export { | |||
18 | 18 | ||
19 | // --------------------------------------------------------------------------- | 19 | // --------------------------------------------------------------------------- |
20 | 20 | ||
21 | async function processCreateView (activity: ActivityView | ActivityCreate, byActor: SignatureActorModel) { | 21 | async function processCreateView (activity: ActivityView | ActivityCreate, byActor: MActorSignature) { |
22 | const videoObject = activity.type === 'View' ? activity.object : (activity.object as ViewObject).object | 22 | const videoObject = activity.type === 'View' ? activity.object : (activity.object as ViewObject).object |
23 | 23 | ||
24 | const options = { | 24 | const options = { |
25 | videoObject: videoObject, | 25 | videoObject, |
26 | fetchType: 'only-video' as 'only-video' | 26 | fetchType: 'only-video' as 'only-video' |
27 | } | 27 | } |
28 | const { video } = await getOrCreateVideoAndAccountAndChannel(options) | 28 | const { video } = await getOrCreateVideoAndAccountAndChannel(options) |
diff --git a/server/lib/activitypub/process/process.ts b/server/lib/activitypub/process/process.ts index d108fe321..c602bf218 100644 --- a/server/lib/activitypub/process/process.ts +++ b/server/lib/activitypub/process/process.ts | |||
@@ -1,7 +1,6 @@ | |||
1 | import { Activity, ActivityType } from '../../../../shared/models/activitypub' | 1 | import { Activity, ActivityType } from '../../../../shared/models/activitypub' |
2 | import { checkUrlsSameHost, getAPId } from '../../../helpers/activitypub' | 2 | import { checkUrlsSameHost, getAPId } from '../../../helpers/activitypub' |
3 | import { logger } from '../../../helpers/logger' | 3 | import { logger } from '../../../helpers/logger' |
4 | import { ActorModel } from '../../../models/activitypub/actor' | ||
5 | import { processAcceptActivity } from './process-accept' | 4 | import { processAcceptActivity } from './process-accept' |
6 | import { processAnnounceActivity } from './process-announce' | 5 | import { processAnnounceActivity } from './process-announce' |
7 | import { processCreateActivity } from './process-create' | 6 | import { processCreateActivity } from './process-create' |
@@ -16,7 +15,7 @@ import { processDislikeActivity } from './process-dislike' | |||
16 | import { processFlagActivity } from './process-flag' | 15 | import { processFlagActivity } from './process-flag' |
17 | import { processViewActivity } from './process-view' | 16 | import { processViewActivity } from './process-view' |
18 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' | 17 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' |
19 | import { SignatureActorModel } from '../../../typings/models' | 18 | import { MActorDefault, MActorSignature } from '../../../typings/models' |
20 | 19 | ||
21 | const processActivity: { [ P in ActivityType ]: (options: APProcessorOptions<Activity>) => Promise<any> } = { | 20 | const processActivity: { [ P in ActivityType ]: (options: APProcessorOptions<Activity>) => Promise<any> } = { |
22 | Create: processCreateActivity, | 21 | Create: processCreateActivity, |
@@ -36,15 +35,15 @@ const processActivity: { [ P in ActivityType ]: (options: APProcessorOptions<Act | |||
36 | async function processActivities ( | 35 | async function processActivities ( |
37 | activities: Activity[], | 36 | activities: Activity[], |
38 | options: { | 37 | options: { |
39 | signatureActor?: SignatureActorModel | 38 | signatureActor?: MActorSignature |
40 | inboxActor?: ActorModel | 39 | inboxActor?: MActorDefault |
41 | outboxUrl?: string | 40 | outboxUrl?: string |
42 | fromFetch?: boolean | 41 | fromFetch?: boolean |
43 | } = {} | 42 | } = {} |
44 | ) { | 43 | ) { |
45 | const { outboxUrl, signatureActor, inboxActor, fromFetch = false } = options | 44 | const { outboxUrl, signatureActor, inboxActor, fromFetch = false } = options |
46 | 45 | ||
47 | const actorsCache: { [ url: string ]: SignatureActorModel } = {} | 46 | const actorsCache: { [ url: string ]: MActorSignature } = {} |
48 | 47 | ||
49 | for (const activity of activities) { | 48 | for (const activity of activities) { |
50 | if (!signatureActor && [ 'Create', 'Announce', 'Like' ].includes(activity.type) === false) { | 49 | if (!signatureActor && [ 'Create', 'Announce', 'Like' ].includes(activity.type) === false) { |
@@ -75,7 +74,7 @@ async function processActivities ( | |||
75 | } | 74 | } |
76 | 75 | ||
77 | try { | 76 | try { |
78 | await activityProcessor({ activity, byActor, inboxActor: inboxActor, fromFetch }) | 77 | await activityProcessor({ activity, byActor, inboxActor, fromFetch }) |
79 | } catch (err) { | 78 | } catch (err) { |
80 | logger.warn('Cannot process activity %s.', activity.type, { err }) | 79 | logger.warn('Cannot process activity %s.', activity.type, { err }) |
81 | } | 80 | } |
diff --git a/server/lib/activitypub/send/send-accept.ts b/server/lib/activitypub/send/send-accept.ts index 813c42e15..9f0225b64 100644 --- a/server/lib/activitypub/send/send-accept.ts +++ b/server/lib/activitypub/send/send-accept.ts | |||
@@ -3,10 +3,9 @@ import { getActorFollowAcceptActivityPubUrl, getActorFollowActivityPubUrl } from | |||
3 | import { unicastTo } from './utils' | 3 | import { unicastTo } from './utils' |
4 | import { buildFollowActivity } from './send-follow' | 4 | import { buildFollowActivity } from './send-follow' |
5 | import { logger } from '../../../helpers/logger' | 5 | import { logger } from '../../../helpers/logger' |
6 | import { ActorFollowModelLight } from '../../../typings/models/actor-follow' | 6 | import { MActor, MActorFollowActors } from '../../../typings/models' |
7 | import { ActorModelOnly } from '../../../typings/models' | ||
8 | 7 | ||
9 | async function sendAccept (actorFollow: ActorFollowModelLight) { | 8 | async function sendAccept (actorFollow: MActorFollowActors) { |
10 | const follower = actorFollow.ActorFollower | 9 | const follower = actorFollow.ActorFollower |
11 | const me = actorFollow.ActorFollowing | 10 | const me = actorFollow.ActorFollowing |
12 | 11 | ||
@@ -34,7 +33,7 @@ export { | |||
34 | 33 | ||
35 | // --------------------------------------------------------------------------- | 34 | // --------------------------------------------------------------------------- |
36 | 35 | ||
37 | function buildAcceptActivity (url: string, byActor: ActorModelOnly, followActivityData: ActivityFollow): ActivityAccept { | 36 | function buildAcceptActivity (url: string, byActor: MActor, followActivityData: ActivityFollow): ActivityAccept { |
38 | return { | 37 | return { |
39 | type: 'Accept', | 38 | type: 'Accept', |
40 | id: url, | 39 | id: url, |
diff --git a/server/lib/activitypub/send/send-announce.ts b/server/lib/activitypub/send/send-announce.ts index 7fe4ca180..a0f33852c 100644 --- a/server/lib/activitypub/send/send-announce.ts +++ b/server/lib/activitypub/send/send-announce.ts | |||
@@ -1,16 +1,15 @@ | |||
1 | import { Transaction } from 'sequelize' | 1 | import { Transaction } from 'sequelize' |
2 | import { ActivityAnnounce, ActivityAudience } from '../../../../shared/models/activitypub' | 2 | import { ActivityAnnounce, ActivityAudience } from '../../../../shared/models/activitypub' |
3 | import { VideoModel } from '../../../models/video/video' | ||
4 | import { broadcastToFollowers } from './utils' | 3 | import { broadcastToFollowers } from './utils' |
5 | import { audiencify, getActorsInvolvedInVideo, getAudience, getAudienceFromFollowersOf } from '../audience' | 4 | import { audiencify, getActorsInvolvedInVideo, getAudience, getAudienceFromFollowersOf } from '../audience' |
6 | import { logger } from '../../../helpers/logger' | 5 | import { logger } from '../../../helpers/logger' |
7 | import { ActorModelOnly } from '../../../typings/models' | 6 | import { MActorLight, MVideo } from '../../../typings/models' |
8 | import { VideoShareModelOnly } from '../../../typings/models/video-share' | 7 | import { MVideoShare } from '../../../typings/models/video' |
9 | 8 | ||
10 | async function buildAnnounceWithVideoAudience ( | 9 | async function buildAnnounceWithVideoAudience ( |
11 | byActor: ActorModelOnly, | 10 | byActor: MActorLight, |
12 | videoShare: VideoShareModelOnly, | 11 | videoShare: MVideoShare, |
13 | video: VideoModel, | 12 | video: MVideo, |
14 | t: Transaction | 13 | t: Transaction |
15 | ) { | 14 | ) { |
16 | const announcedObject = video.url | 15 | const announcedObject = video.url |
@@ -23,7 +22,7 @@ async function buildAnnounceWithVideoAudience ( | |||
23 | return { activity, actorsInvolvedInVideo } | 22 | return { activity, actorsInvolvedInVideo } |
24 | } | 23 | } |
25 | 24 | ||
26 | async function sendVideoAnnounce (byActor: ActorModelOnly, videoShare: VideoShareModelOnly, video: VideoModel, t: Transaction) { | 25 | async function sendVideoAnnounce (byActor: MActorLight, videoShare: MVideoShare, video: MVideo, t: Transaction) { |
27 | const { activity, actorsInvolvedInVideo } = await buildAnnounceWithVideoAudience(byActor, videoShare, video, t) | 26 | const { activity, actorsInvolvedInVideo } = await buildAnnounceWithVideoAudience(byActor, videoShare, video, t) |
28 | 27 | ||
29 | logger.info('Creating job to send announce %s.', videoShare.url) | 28 | logger.info('Creating job to send announce %s.', videoShare.url) |
@@ -32,7 +31,7 @@ async function sendVideoAnnounce (byActor: ActorModelOnly, videoShare: VideoShar | |||
32 | return broadcastToFollowers(activity, byActor, actorsInvolvedInVideo, t, followersException) | 31 | return broadcastToFollowers(activity, byActor, actorsInvolvedInVideo, t, followersException) |
33 | } | 32 | } |
34 | 33 | ||
35 | function buildAnnounceActivity (url: string, byActor: ActorModelOnly, object: string, audience?: ActivityAudience): ActivityAnnounce { | 34 | function buildAnnounceActivity (url: string, byActor: MActorLight, object: string, audience?: ActivityAudience): ActivityAnnounce { |
36 | if (!audience) audience = getAudience(byActor) | 35 | if (!audience) audience = getAudience(byActor) |
37 | 36 | ||
38 | return audiencify({ | 37 | return audiencify({ |
diff --git a/server/lib/activitypub/send/send-create.ts b/server/lib/activitypub/send/send-create.ts index 9c21149f2..26ec3e948 100644 --- a/server/lib/activitypub/send/send-create.ts +++ b/server/lib/activitypub/send/send-create.ts | |||
@@ -1,19 +1,23 @@ | |||
1 | import { Transaction } from 'sequelize' | 1 | import { Transaction } from 'sequelize' |
2 | import { ActivityAudience, ActivityCreate } from '../../../../shared/models/activitypub' | 2 | import { ActivityAudience, ActivityCreate } from '../../../../shared/models/activitypub' |
3 | import { VideoPrivacy } from '../../../../shared/models/videos' | 3 | import { VideoPrivacy } from '../../../../shared/models/videos' |
4 | import { ActorModel } from '../../../models/activitypub/actor' | ||
5 | import { VideoModel } from '../../../models/video/video' | ||
6 | import { VideoCommentModel } from '../../../models/video/video-comment' | 4 | import { VideoCommentModel } from '../../../models/video/video-comment' |
7 | import { broadcastToActors, broadcastToFollowers, sendVideoRelatedActivity, unicastTo } from './utils' | 5 | import { broadcastToActors, broadcastToFollowers, sendVideoRelatedActivity, unicastTo } from './utils' |
8 | import { audiencify, getActorsInvolvedInVideo, getAudience, getAudienceFromFollowersOf, getVideoCommentAudience } from '../audience' | 6 | import { audiencify, getActorsInvolvedInVideo, getAudience, getAudienceFromFollowersOf, getVideoCommentAudience } from '../audience' |
9 | import { logger } from '../../../helpers/logger' | 7 | import { logger } from '../../../helpers/logger' |
10 | import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy' | ||
11 | import { VideoPlaylistModel } from '../../../models/video/video-playlist' | ||
12 | import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model' | 8 | import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model' |
13 | import { getServerActor } from '../../../helpers/utils' | 9 | import { getServerActor } from '../../../helpers/utils' |
14 | import * as Bluebird from 'bluebird' | 10 | import { |
15 | 11 | MActorLight, | |
16 | async function sendCreateVideo (video: VideoModel, t: Transaction) { | 12 | MCommentOwnerVideo, |
13 | MVideoAccountLight, | ||
14 | MVideoAP, | ||
15 | MVideoPlaylistFull, | ||
16 | MVideoRedundancyFileVideo, | ||
17 | MVideoRedundancyStreamingPlaylistVideo | ||
18 | } from '../../../typings/models' | ||
19 | |||
20 | async function sendCreateVideo (video: MVideoAP, t: Transaction) { | ||
17 | if (video.privacy === VideoPrivacy.PRIVATE) return undefined | 21 | if (video.privacy === VideoPrivacy.PRIVATE) return undefined |
18 | 22 | ||
19 | logger.info('Creating job to send video creation of %s.', video.url) | 23 | logger.info('Creating job to send video creation of %s.', video.url) |
@@ -27,7 +31,11 @@ async function sendCreateVideo (video: VideoModel, t: Transaction) { | |||
27 | return broadcastToFollowers(createActivity, byActor, [ byActor ], t) | 31 | return broadcastToFollowers(createActivity, byActor, [ byActor ], t) |
28 | } | 32 | } |
29 | 33 | ||
30 | async function sendCreateCacheFile (byActor: ActorModel, video: VideoModel, fileRedundancy: VideoRedundancyModel) { | 34 | async function sendCreateCacheFile ( |
35 | byActor: MActorLight, | ||
36 | video: MVideoAccountLight, | ||
37 | fileRedundancy: MVideoRedundancyStreamingPlaylistVideo | MVideoRedundancyFileVideo | ||
38 | ) { | ||
31 | logger.info('Creating job to send file cache of %s.', fileRedundancy.url) | 39 | logger.info('Creating job to send file cache of %s.', fileRedundancy.url) |
32 | 40 | ||
33 | return sendVideoRelatedCreateActivity({ | 41 | return sendVideoRelatedCreateActivity({ |
@@ -38,7 +46,7 @@ async function sendCreateCacheFile (byActor: ActorModel, video: VideoModel, file | |||
38 | }) | 46 | }) |
39 | } | 47 | } |
40 | 48 | ||
41 | async function sendCreateVideoPlaylist (playlist: VideoPlaylistModel, t: Transaction) { | 49 | async function sendCreateVideoPlaylist (playlist: MVideoPlaylistFull, t: Transaction) { |
42 | if (playlist.privacy === VideoPlaylistPrivacy.PRIVATE) return undefined | 50 | if (playlist.privacy === VideoPlaylistPrivacy.PRIVATE) return undefined |
43 | 51 | ||
44 | logger.info('Creating job to send create video playlist of %s.', playlist.url) | 52 | logger.info('Creating job to send create video playlist of %s.', playlist.url) |
@@ -57,7 +65,7 @@ async function sendCreateVideoPlaylist (playlist: VideoPlaylistModel, t: Transac | |||
57 | return broadcastToFollowers(createActivity, byActor, toFollowersOf, t) | 65 | return broadcastToFollowers(createActivity, byActor, toFollowersOf, t) |
58 | } | 66 | } |
59 | 67 | ||
60 | async function sendCreateVideoComment (comment: VideoCommentModel, t: Transaction) { | 68 | async function sendCreateVideoComment (comment: MCommentOwnerVideo, t: Transaction) { |
61 | logger.info('Creating job to send comment %s.', comment.url) | 69 | logger.info('Creating job to send comment %s.', comment.url) |
62 | 70 | ||
63 | const isOrigin = comment.Video.isOwned() | 71 | const isOrigin = comment.Video.isOwned() |
@@ -95,7 +103,7 @@ async function sendCreateVideoComment (comment: VideoCommentModel, t: Transactio | |||
95 | t.afterCommit(() => unicastTo(createActivity, byActor, comment.Video.VideoChannel.Account.Actor.sharedInboxUrl)) | 103 | t.afterCommit(() => unicastTo(createActivity, byActor, comment.Video.VideoChannel.Account.Actor.sharedInboxUrl)) |
96 | } | 104 | } |
97 | 105 | ||
98 | function buildCreateActivity (url: string, byActor: ActorModel, object: any, audience?: ActivityAudience): ActivityCreate { | 106 | function buildCreateActivity (url: string, byActor: MActorLight, object: any, audience?: ActivityAudience): ActivityCreate { |
99 | if (!audience) audience = getAudience(byActor) | 107 | if (!audience) audience = getAudience(byActor) |
100 | 108 | ||
101 | return audiencify( | 109 | return audiencify( |
@@ -122,8 +130,8 @@ export { | |||
122 | // --------------------------------------------------------------------------- | 130 | // --------------------------------------------------------------------------- |
123 | 131 | ||
124 | async function sendVideoRelatedCreateActivity (options: { | 132 | async function sendVideoRelatedCreateActivity (options: { |
125 | byActor: ActorModel, | 133 | byActor: MActorLight, |
126 | video: VideoModel, | 134 | video: MVideoAccountLight, |
127 | url: string, | 135 | url: string, |
128 | object: any, | 136 | object: any, |
129 | transaction?: Transaction | 137 | transaction?: Transaction |
diff --git a/server/lib/activitypub/send/send-delete.ts b/server/lib/activitypub/send/send-delete.ts index 6c7fb8449..4b1ff8dc5 100644 --- a/server/lib/activitypub/send/send-delete.ts +++ b/server/lib/activitypub/send/send-delete.ts | |||
@@ -1,17 +1,17 @@ | |||
1 | import { Transaction } from 'sequelize' | 1 | import { Transaction } from 'sequelize' |
2 | import { ActivityAudience, ActivityDelete } from '../../../../shared/models/activitypub' | 2 | import { ActivityAudience, ActivityDelete } from '../../../../shared/models/activitypub' |
3 | import { ActorModel } from '../../../models/activitypub/actor' | 3 | import { ActorModel } from '../../../models/activitypub/actor' |
4 | import { VideoModel } from '../../../models/video/video' | ||
5 | import { VideoCommentModel } from '../../../models/video/video-comment' | 4 | import { VideoCommentModel } from '../../../models/video/video-comment' |
6 | import { VideoShareModel } from '../../../models/video/video-share' | 5 | import { VideoShareModel } from '../../../models/video/video-share' |
7 | import { getDeleteActivityPubUrl } from '../url' | 6 | import { getDeleteActivityPubUrl } from '../url' |
8 | import { broadcastToActors, broadcastToFollowers, sendVideoRelatedActivity, unicastTo } from './utils' | 7 | import { broadcastToActors, broadcastToFollowers, sendVideoRelatedActivity, unicastTo } from './utils' |
9 | import { audiencify, getActorsInvolvedInVideo, getVideoCommentAudience } from '../audience' | 8 | import { audiencify, getActorsInvolvedInVideo, getVideoCommentAudience } from '../audience' |
10 | import { logger } from '../../../helpers/logger' | 9 | import { logger } from '../../../helpers/logger' |
11 | import { VideoPlaylistModel } from '../../../models/video/video-playlist' | ||
12 | import { getServerActor } from '../../../helpers/utils' | 10 | import { getServerActor } from '../../../helpers/utils' |
11 | import { MCommentOwnerVideoReply, MVideoAccountLight, MVideoPlaylistFullSummary } from '../../../typings/models/video' | ||
12 | import { MActorUrl } from '../../../typings/models' | ||
13 | 13 | ||
14 | async function sendDeleteVideo (video: VideoModel, transaction: Transaction) { | 14 | async function sendDeleteVideo (video: MVideoAccountLight, transaction: Transaction) { |
15 | logger.info('Creating job to broadcast delete of video %s.', video.url) | 15 | logger.info('Creating job to broadcast delete of video %s.', video.url) |
16 | 16 | ||
17 | const byActor = video.VideoChannel.Account.Actor | 17 | const byActor = video.VideoChannel.Account.Actor |
@@ -42,7 +42,7 @@ async function sendDeleteActor (byActor: ActorModel, t: Transaction) { | |||
42 | return broadcastToFollowers(activity, byActor, actorsInvolved, t) | 42 | return broadcastToFollowers(activity, byActor, actorsInvolved, t) |
43 | } | 43 | } |
44 | 44 | ||
45 | async function sendDeleteVideoComment (videoComment: VideoCommentModel, t: Transaction) { | 45 | async function sendDeleteVideoComment (videoComment: MCommentOwnerVideoReply, t: Transaction) { |
46 | logger.info('Creating job to send delete of comment %s.', videoComment.url) | 46 | logger.info('Creating job to send delete of comment %s.', videoComment.url) |
47 | 47 | ||
48 | const isVideoOrigin = videoComment.Video.isOwned() | 48 | const isVideoOrigin = videoComment.Video.isOwned() |
@@ -74,7 +74,7 @@ async function sendDeleteVideoComment (videoComment: VideoCommentModel, t: Trans | |||
74 | t.afterCommit(() => unicastTo(activity, byActor, videoComment.Video.VideoChannel.Account.Actor.sharedInboxUrl)) | 74 | t.afterCommit(() => unicastTo(activity, byActor, videoComment.Video.VideoChannel.Account.Actor.sharedInboxUrl)) |
75 | } | 75 | } |
76 | 76 | ||
77 | async function sendDeleteVideoPlaylist (videoPlaylist: VideoPlaylistModel, t: Transaction) { | 77 | async function sendDeleteVideoPlaylist (videoPlaylist: MVideoPlaylistFullSummary, t: Transaction) { |
78 | logger.info('Creating job to send delete of playlist %s.', videoPlaylist.url) | 78 | logger.info('Creating job to send delete of playlist %s.', videoPlaylist.url) |
79 | 79 | ||
80 | const byActor = videoPlaylist.OwnerAccount.Actor | 80 | const byActor = videoPlaylist.OwnerAccount.Actor |
@@ -101,7 +101,7 @@ export { | |||
101 | 101 | ||
102 | // --------------------------------------------------------------------------- | 102 | // --------------------------------------------------------------------------- |
103 | 103 | ||
104 | function buildDeleteActivity (url: string, object: string, byActor: ActorModel, audience?: ActivityAudience): ActivityDelete { | 104 | function buildDeleteActivity (url: string, object: string, byActor: MActorUrl, audience?: ActivityAudience): ActivityDelete { |
105 | const activity = { | 105 | const activity = { |
106 | type: 'Delete' as 'Delete', | 106 | type: 'Delete' as 'Delete', |
107 | id: url, | 107 | id: url, |
diff --git a/server/lib/activitypub/send/send-dislike.ts b/server/lib/activitypub/send/send-dislike.ts index a88436f2c..6e41f241f 100644 --- a/server/lib/activitypub/send/send-dislike.ts +++ b/server/lib/activitypub/send/send-dislike.ts | |||
@@ -1,13 +1,12 @@ | |||
1 | import { Transaction } from 'sequelize' | 1 | import { Transaction } from 'sequelize' |
2 | import { ActorModel } from '../../../models/activitypub/actor' | ||
3 | import { VideoModel } from '../../../models/video/video' | ||
4 | import { getVideoDislikeActivityPubUrl } from '../url' | 2 | import { getVideoDislikeActivityPubUrl } from '../url' |
5 | import { logger } from '../../../helpers/logger' | 3 | import { logger } from '../../../helpers/logger' |
6 | import { ActivityAudience, ActivityDislike } from '../../../../shared/models/activitypub' | 4 | import { ActivityAudience, ActivityDislike } from '../../../../shared/models/activitypub' |
7 | import { sendVideoRelatedActivity } from './utils' | 5 | import { sendVideoRelatedActivity } from './utils' |
8 | import { audiencify, getAudience } from '../audience' | 6 | import { audiencify, getAudience } from '../audience' |
7 | import { MActor, MActorAudience, MVideoAccountLight, MVideoUrl } from '../../../typings/models' | ||
9 | 8 | ||
10 | async function sendDislike (byActor: ActorModel, video: VideoModel, t: Transaction) { | 9 | async function sendDislike (byActor: MActor, video: MVideoAccountLight, t: Transaction) { |
11 | logger.info('Creating job to dislike %s.', video.url) | 10 | logger.info('Creating job to dislike %s.', video.url) |
12 | 11 | ||
13 | const activityBuilder = (audience: ActivityAudience) => { | 12 | const activityBuilder = (audience: ActivityAudience) => { |
@@ -19,7 +18,7 @@ async function sendDislike (byActor: ActorModel, video: VideoModel, t: Transacti | |||
19 | return sendVideoRelatedActivity(activityBuilder, { byActor, video, transaction: t }) | 18 | return sendVideoRelatedActivity(activityBuilder, { byActor, video, transaction: t }) |
20 | } | 19 | } |
21 | 20 | ||
22 | function buildDislikeActivity (url: string, byActor: ActorModel, video: VideoModel, audience?: ActivityAudience): ActivityDislike { | 21 | function buildDislikeActivity (url: string, byActor: MActorAudience, video: MVideoUrl, audience?: ActivityAudience): ActivityDislike { |
23 | if (!audience) audience = getAudience(byActor) | 22 | if (!audience) audience = getAudience(byActor) |
24 | 23 | ||
25 | return audiencify( | 24 | return audiencify( |
diff --git a/server/lib/activitypub/send/send-flag.ts b/server/lib/activitypub/send/send-flag.ts index 61ee389a6..5ae1614ab 100644 --- a/server/lib/activitypub/send/send-flag.ts +++ b/server/lib/activitypub/send/send-flag.ts | |||
@@ -1,14 +1,13 @@ | |||
1 | import { ActorModel } from '../../../models/activitypub/actor' | ||
2 | import { VideoModel } from '../../../models/video/video' | ||
3 | import { VideoAbuseModel } from '../../../models/video/video-abuse' | ||
4 | import { getVideoAbuseActivityPubUrl } from '../url' | 1 | import { getVideoAbuseActivityPubUrl } from '../url' |
5 | import { unicastTo } from './utils' | 2 | import { unicastTo } from './utils' |
6 | import { logger } from '../../../helpers/logger' | 3 | import { logger } from '../../../helpers/logger' |
7 | import { ActivityAudience, ActivityFlag } from '../../../../shared/models/activitypub' | 4 | import { ActivityAudience, ActivityFlag } from '../../../../shared/models/activitypub' |
8 | import { audiencify, getAudience } from '../audience' | 5 | import { audiencify, getAudience } from '../audience' |
9 | import { Transaction } from 'sequelize' | 6 | import { Transaction } from 'sequelize' |
7 | import { MActor, MVideoFullLight } from '../../../typings/models' | ||
8 | import { MVideoAbuseVideo } from '../../../typings/models/video' | ||
10 | 9 | ||
11 | async function sendVideoAbuse (byActor: ActorModel, videoAbuse: VideoAbuseModel, video: VideoModel, t: Transaction) { | 10 | async function sendVideoAbuse (byActor: MActor, videoAbuse: MVideoAbuseVideo, video: MVideoFullLight, t: Transaction) { |
12 | if (!video.VideoChannel.Account.Actor.serverId) return // Local user | 11 | if (!video.VideoChannel.Account.Actor.serverId) return // Local user |
13 | 12 | ||
14 | const url = getVideoAbuseActivityPubUrl(videoAbuse) | 13 | const url = getVideoAbuseActivityPubUrl(videoAbuse) |
@@ -22,7 +21,7 @@ async function sendVideoAbuse (byActor: ActorModel, videoAbuse: VideoAbuseModel, | |||
22 | t.afterCommit(() => unicastTo(flagActivity, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl)) | 21 | t.afterCommit(() => unicastTo(flagActivity, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl)) |
23 | } | 22 | } |
24 | 23 | ||
25 | function buildFlagActivity (url: string, byActor: ActorModel, videoAbuse: VideoAbuseModel, audience: ActivityAudience): ActivityFlag { | 24 | function buildFlagActivity (url: string, byActor: MActor, videoAbuse: MVideoAbuseVideo, audience: ActivityAudience): ActivityFlag { |
26 | if (!audience) audience = getAudience(byActor) | 25 | if (!audience) audience = getAudience(byActor) |
27 | 26 | ||
28 | const activity = Object.assign( | 27 | const activity = Object.assign( |
diff --git a/server/lib/activitypub/send/send-follow.ts b/server/lib/activitypub/send/send-follow.ts index a59ed50cf..ce400d8ff 100644 --- a/server/lib/activitypub/send/send-follow.ts +++ b/server/lib/activitypub/send/send-follow.ts | |||
@@ -1,12 +1,11 @@ | |||
1 | import { ActivityFollow } from '../../../../shared/models/activitypub' | 1 | import { ActivityFollow } from '../../../../shared/models/activitypub' |
2 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' | ||
3 | import { getActorFollowActivityPubUrl } from '../url' | 2 | import { getActorFollowActivityPubUrl } from '../url' |
4 | import { unicastTo } from './utils' | 3 | import { unicastTo } from './utils' |
5 | import { logger } from '../../../helpers/logger' | 4 | import { logger } from '../../../helpers/logger' |
6 | import { Transaction } from 'sequelize' | 5 | import { Transaction } from 'sequelize' |
7 | import { ActorModelOnly } from '../../../typings/models' | 6 | import { MActor, MActorFollowActors } from '../../../typings/models' |
8 | 7 | ||
9 | function sendFollow (actorFollow: ActorFollowModel, t: Transaction) { | 8 | function sendFollow (actorFollow: MActorFollowActors, t: Transaction) { |
10 | const me = actorFollow.ActorFollower | 9 | const me = actorFollow.ActorFollower |
11 | const following = actorFollow.ActorFollowing | 10 | const following = actorFollow.ActorFollowing |
12 | 11 | ||
@@ -21,7 +20,7 @@ function sendFollow (actorFollow: ActorFollowModel, t: Transaction) { | |||
21 | t.afterCommit(() => unicastTo(data, me, following.inboxUrl)) | 20 | t.afterCommit(() => unicastTo(data, me, following.inboxUrl)) |
22 | } | 21 | } |
23 | 22 | ||
24 | function buildFollowActivity (url: string, byActor: ActorModelOnly, targetActor: ActorModelOnly): ActivityFollow { | 23 | function buildFollowActivity (url: string, byActor: MActor, targetActor: MActor): ActivityFollow { |
25 | return { | 24 | return { |
26 | type: 'Follow', | 25 | type: 'Follow', |
27 | id: url, | 26 | id: url, |
diff --git a/server/lib/activitypub/send/send-like.ts b/server/lib/activitypub/send/send-like.ts index 35227887a..e84a6f98b 100644 --- a/server/lib/activitypub/send/send-like.ts +++ b/server/lib/activitypub/send/send-like.ts | |||
@@ -1,13 +1,12 @@ | |||
1 | import { Transaction } from 'sequelize' | 1 | import { Transaction } from 'sequelize' |
2 | import { ActivityAudience, ActivityLike } from '../../../../shared/models/activitypub' | 2 | import { ActivityAudience, ActivityLike } from '../../../../shared/models/activitypub' |
3 | import { ActorModel } from '../../../models/activitypub/actor' | ||
4 | import { VideoModel } from '../../../models/video/video' | ||
5 | import { getVideoLikeActivityPubUrl } from '../url' | 3 | import { getVideoLikeActivityPubUrl } from '../url' |
6 | import { sendVideoRelatedActivity } from './utils' | 4 | import { sendVideoRelatedActivity } from './utils' |
7 | import { audiencify, getAudience } from '../audience' | 5 | import { audiencify, getAudience } from '../audience' |
8 | import { logger } from '../../../helpers/logger' | 6 | import { logger } from '../../../helpers/logger' |
7 | import { MActor, MActorAudience, MVideoAccountLight, MVideoUrl } from '../../../typings/models' | ||
9 | 8 | ||
10 | async function sendLike (byActor: ActorModel, video: VideoModel, t: Transaction) { | 9 | async function sendLike (byActor: MActor, video: MVideoAccountLight, t: Transaction) { |
11 | logger.info('Creating job to like %s.', video.url) | 10 | logger.info('Creating job to like %s.', video.url) |
12 | 11 | ||
13 | const activityBuilder = (audience: ActivityAudience) => { | 12 | const activityBuilder = (audience: ActivityAudience) => { |
@@ -19,7 +18,7 @@ async function sendLike (byActor: ActorModel, video: VideoModel, t: Transaction) | |||
19 | return sendVideoRelatedActivity(activityBuilder, { byActor, video, transaction: t }) | 18 | return sendVideoRelatedActivity(activityBuilder, { byActor, video, transaction: t }) |
20 | } | 19 | } |
21 | 20 | ||
22 | function buildLikeActivity (url: string, byActor: ActorModel, video: VideoModel, audience?: ActivityAudience): ActivityLike { | 21 | function buildLikeActivity (url: string, byActor: MActorAudience, video: MVideoUrl, audience?: ActivityAudience): ActivityLike { |
23 | if (!audience) audience = getAudience(byActor) | 22 | if (!audience) audience = getAudience(byActor) |
24 | 23 | ||
25 | return audiencify( | 24 | return audiencify( |
diff --git a/server/lib/activitypub/send/send-reject.ts b/server/lib/activitypub/send/send-reject.ts index 63110b433..4258a3c36 100644 --- a/server/lib/activitypub/send/send-reject.ts +++ b/server/lib/activitypub/send/send-reject.ts | |||
@@ -1,12 +1,11 @@ | |||
1 | import { ActivityFollow, ActivityReject } from '../../../../shared/models/activitypub' | 1 | import { ActivityFollow, ActivityReject } from '../../../../shared/models/activitypub' |
2 | import { ActorModel } from '../../../models/activitypub/actor' | ||
3 | import { getActorFollowActivityPubUrl, getActorFollowRejectActivityPubUrl } from '../url' | 2 | import { getActorFollowActivityPubUrl, getActorFollowRejectActivityPubUrl } from '../url' |
4 | import { unicastTo } from './utils' | 3 | import { unicastTo } from './utils' |
5 | import { buildFollowActivity } from './send-follow' | 4 | import { buildFollowActivity } from './send-follow' |
6 | import { logger } from '../../../helpers/logger' | 5 | import { logger } from '../../../helpers/logger' |
7 | import { SignatureActorModel } from '../../../typings/models' | 6 | import { MActor } from '../../../typings/models' |
8 | 7 | ||
9 | async function sendReject (follower: SignatureActorModel, following: ActorModel) { | 8 | async function sendReject (follower: MActor, following: MActor) { |
10 | if (!follower.serverId) { // This should never happen | 9 | if (!follower.serverId) { // This should never happen |
11 | logger.warn('Do not sending reject to local follower.') | 10 | logger.warn('Do not sending reject to local follower.') |
12 | return | 11 | return |
@@ -31,7 +30,7 @@ export { | |||
31 | 30 | ||
32 | // --------------------------------------------------------------------------- | 31 | // --------------------------------------------------------------------------- |
33 | 32 | ||
34 | function buildRejectActivity (url: string, byActor: ActorModel, followActivityData: ActivityFollow): ActivityReject { | 33 | function buildRejectActivity (url: string, byActor: MActor, followActivityData: ActivityFollow): ActivityReject { |
35 | return { | 34 | return { |
36 | type: 'Reject', | 35 | type: 'Reject', |
37 | id: url, | 36 | id: url, |
diff --git a/server/lib/activitypub/send/send-undo.ts b/server/lib/activitypub/send/send-undo.ts index 8fcbbac5c..e9ab5b3c5 100644 --- a/server/lib/activitypub/send/send-undo.ts +++ b/server/lib/activitypub/send/send-undo.ts | |||
@@ -2,13 +2,12 @@ import { Transaction } from 'sequelize' | |||
2 | import { | 2 | import { |
3 | ActivityAnnounce, | 3 | ActivityAnnounce, |
4 | ActivityAudience, | 4 | ActivityAudience, |
5 | ActivityCreate, ActivityDislike, | 5 | ActivityCreate, |
6 | ActivityDislike, | ||
6 | ActivityFollow, | 7 | ActivityFollow, |
7 | ActivityLike, | 8 | ActivityLike, |
8 | ActivityUndo | 9 | ActivityUndo |
9 | } from '../../../../shared/models/activitypub' | 10 | } from '../../../../shared/models/activitypub' |
10 | import { ActorModel } from '../../../models/activitypub/actor' | ||
11 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' | ||
12 | import { VideoModel } from '../../../models/video/video' | 11 | import { VideoModel } from '../../../models/video/video' |
13 | import { getActorFollowActivityPubUrl, getUndoActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoLikeActivityPubUrl } from '../url' | 12 | import { getActorFollowActivityPubUrl, getUndoActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoLikeActivityPubUrl } from '../url' |
14 | import { broadcastToFollowers, sendVideoRelatedActivity, unicastTo } from './utils' | 13 | import { broadcastToFollowers, sendVideoRelatedActivity, unicastTo } from './utils' |
@@ -16,13 +15,20 @@ import { audiencify, getAudience } from '../audience' | |||
16 | import { buildCreateActivity } from './send-create' | 15 | import { buildCreateActivity } from './send-create' |
17 | import { buildFollowActivity } from './send-follow' | 16 | import { buildFollowActivity } from './send-follow' |
18 | import { buildLikeActivity } from './send-like' | 17 | import { buildLikeActivity } from './send-like' |
19 | import { VideoShareModel } from '../../../models/video/video-share' | ||
20 | import { buildAnnounceWithVideoAudience } from './send-announce' | 18 | import { buildAnnounceWithVideoAudience } from './send-announce' |
21 | import { logger } from '../../../helpers/logger' | 19 | import { logger } from '../../../helpers/logger' |
22 | import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy' | ||
23 | import { buildDislikeActivity } from './send-dislike' | 20 | import { buildDislikeActivity } from './send-dislike' |
24 | 21 | import { | |
25 | async function sendUndoFollow (actorFollow: ActorFollowModel, t: Transaction) { | 22 | MActor, MActorAudience, |
23 | MActorFollowActors, | ||
24 | MActorLight, | ||
25 | MVideo, | ||
26 | MVideoAccountLight, | ||
27 | MVideoRedundancyVideo, | ||
28 | MVideoShare | ||
29 | } from '../../../typings/models' | ||
30 | |||
31 | async function sendUndoFollow (actorFollow: MActorFollowActors, t: Transaction) { | ||
26 | const me = actorFollow.ActorFollower | 32 | const me = actorFollow.ActorFollower |
27 | const following = actorFollow.ActorFollowing | 33 | const following = actorFollow.ActorFollowing |
28 | 34 | ||
@@ -40,7 +46,7 @@ async function sendUndoFollow (actorFollow: ActorFollowModel, t: Transaction) { | |||
40 | t.afterCommit(() => unicastTo(undoActivity, me, following.inboxUrl)) | 46 | t.afterCommit(() => unicastTo(undoActivity, me, following.inboxUrl)) |
41 | } | 47 | } |
42 | 48 | ||
43 | async function sendUndoAnnounce (byActor: ActorModel, videoShare: VideoShareModel, video: VideoModel, t: Transaction) { | 49 | async function sendUndoAnnounce (byActor: MActorLight, videoShare: MVideoShare, video: MVideo, t: Transaction) { |
44 | logger.info('Creating job to undo announce %s.', videoShare.url) | 50 | logger.info('Creating job to undo announce %s.', videoShare.url) |
45 | 51 | ||
46 | const undoUrl = getUndoActivityPubUrl(videoShare.url) | 52 | const undoUrl = getUndoActivityPubUrl(videoShare.url) |
@@ -52,7 +58,7 @@ async function sendUndoAnnounce (byActor: ActorModel, videoShare: VideoShareMode | |||
52 | return broadcastToFollowers(undoActivity, byActor, actorsInvolvedInVideo, t, followersException) | 58 | return broadcastToFollowers(undoActivity, byActor, actorsInvolvedInVideo, t, followersException) |
53 | } | 59 | } |
54 | 60 | ||
55 | async function sendUndoLike (byActor: ActorModel, video: VideoModel, t: Transaction) { | 61 | async function sendUndoLike (byActor: MActor, video: MVideoAccountLight, t: Transaction) { |
56 | logger.info('Creating job to undo a like of video %s.', video.url) | 62 | logger.info('Creating job to undo a like of video %s.', video.url) |
57 | 63 | ||
58 | const likeUrl = getVideoLikeActivityPubUrl(byActor, video) | 64 | const likeUrl = getVideoLikeActivityPubUrl(byActor, video) |
@@ -61,7 +67,7 @@ async function sendUndoLike (byActor: ActorModel, video: VideoModel, t: Transact | |||
61 | return sendUndoVideoRelatedActivity({ byActor, video, url: likeUrl, activity: likeActivity, transaction: t }) | 67 | return sendUndoVideoRelatedActivity({ byActor, video, url: likeUrl, activity: likeActivity, transaction: t }) |
62 | } | 68 | } |
63 | 69 | ||
64 | async function sendUndoDislike (byActor: ActorModel, video: VideoModel, t: Transaction) { | 70 | async function sendUndoDislike (byActor: MActor, video: MVideoAccountLight, t: Transaction) { |
65 | logger.info('Creating job to undo a dislike of video %s.', video.url) | 71 | logger.info('Creating job to undo a dislike of video %s.', video.url) |
66 | 72 | ||
67 | const dislikeUrl = getVideoDislikeActivityPubUrl(byActor, video) | 73 | const dislikeUrl = getVideoDislikeActivityPubUrl(byActor, video) |
@@ -70,7 +76,7 @@ async function sendUndoDislike (byActor: ActorModel, video: VideoModel, t: Trans | |||
70 | return sendUndoVideoRelatedActivity({ byActor, video, url: dislikeUrl, activity: dislikeActivity, transaction: t }) | 76 | return sendUndoVideoRelatedActivity({ byActor, video, url: dislikeUrl, activity: dislikeActivity, transaction: t }) |
71 | } | 77 | } |
72 | 78 | ||
73 | async function sendUndoCacheFile (byActor: ActorModel, redundancyModel: VideoRedundancyModel, t: Transaction) { | 79 | async function sendUndoCacheFile (byActor: MActor, redundancyModel: MVideoRedundancyVideo, t: Transaction) { |
74 | logger.info('Creating job to undo cache file %s.', redundancyModel.url) | 80 | logger.info('Creating job to undo cache file %s.', redundancyModel.url) |
75 | 81 | ||
76 | const videoId = redundancyModel.getVideo().id | 82 | const videoId = redundancyModel.getVideo().id |
@@ -94,7 +100,7 @@ export { | |||
94 | 100 | ||
95 | function undoActivityData ( | 101 | function undoActivityData ( |
96 | url: string, | 102 | url: string, |
97 | byActor: ActorModel, | 103 | byActor: MActorAudience, |
98 | object: ActivityFollow | ActivityLike | ActivityDislike | ActivityCreate | ActivityAnnounce, | 104 | object: ActivityFollow | ActivityLike | ActivityDislike | ActivityCreate | ActivityAnnounce, |
99 | audience?: ActivityAudience | 105 | audience?: ActivityAudience |
100 | ): ActivityUndo { | 106 | ): ActivityUndo { |
@@ -112,8 +118,8 @@ function undoActivityData ( | |||
112 | } | 118 | } |
113 | 119 | ||
114 | async function sendUndoVideoRelatedActivity (options: { | 120 | async function sendUndoVideoRelatedActivity (options: { |
115 | byActor: ActorModel, | 121 | byActor: MActor, |
116 | video: VideoModel, | 122 | video: MVideoAccountLight, |
117 | url: string, | 123 | url: string, |
118 | activity: ActivityFollow | ActivityLike | ActivityDislike | ActivityCreate | ActivityAnnounce, | 124 | activity: ActivityFollow | ActivityLike | ActivityDislike | ActivityCreate | ActivityAnnounce, |
119 | transaction: Transaction | 125 | transaction: Transaction |
diff --git a/server/lib/activitypub/send/send-update.ts b/server/lib/activitypub/send/send-update.ts index 5bf092894..37517c2be 100644 --- a/server/lib/activitypub/send/send-update.ts +++ b/server/lib/activitypub/send/send-update.ts | |||
@@ -2,21 +2,29 @@ import { Transaction } from 'sequelize' | |||
2 | import { ActivityAudience, ActivityUpdate } from '../../../../shared/models/activitypub' | 2 | import { ActivityAudience, ActivityUpdate } from '../../../../shared/models/activitypub' |
3 | import { VideoPrivacy } from '../../../../shared/models/videos' | 3 | import { VideoPrivacy } from '../../../../shared/models/videos' |
4 | import { AccountModel } from '../../../models/account/account' | 4 | import { AccountModel } from '../../../models/account/account' |
5 | import { ActorModel } from '../../../models/activitypub/actor' | ||
6 | import { VideoModel } from '../../../models/video/video' | 5 | import { VideoModel } from '../../../models/video/video' |
7 | import { VideoChannelModel } from '../../../models/video/video-channel' | ||
8 | import { VideoShareModel } from '../../../models/video/video-share' | 6 | import { VideoShareModel } from '../../../models/video/video-share' |
9 | import { getUpdateActivityPubUrl } from '../url' | 7 | import { getUpdateActivityPubUrl } from '../url' |
10 | import { broadcastToFollowers, sendVideoRelatedActivity } from './utils' | 8 | import { broadcastToFollowers, sendVideoRelatedActivity } from './utils' |
11 | import { audiencify, getActorsInvolvedInVideo, getAudience } from '../audience' | 9 | import { audiencify, getActorsInvolvedInVideo, getAudience } from '../audience' |
12 | import { logger } from '../../../helpers/logger' | 10 | import { logger } from '../../../helpers/logger' |
13 | import { VideoCaptionModel } from '../../../models/video/video-caption' | 11 | import { VideoCaptionModel } from '../../../models/video/video-caption' |
14 | import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy' | ||
15 | import { VideoPlaylistModel } from '../../../models/video/video-playlist' | ||
16 | import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model' | 12 | import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model' |
17 | import { getServerActor } from '../../../helpers/utils' | 13 | import { getServerActor } from '../../../helpers/utils' |
14 | import { | ||
15 | MAccountDefault, | ||
16 | MActor, | ||
17 | MActorLight, | ||
18 | MChannelDefault, | ||
19 | MVideoAP, | ||
20 | MVideoAPWithoutCaption, | ||
21 | MVideoPlaylistFull, | ||
22 | MVideoRedundancyVideo | ||
23 | } from '../../../typings/models' | ||
24 | |||
25 | async function sendUpdateVideo (videoArg: MVideoAPWithoutCaption, t: Transaction, overrodeByActor?: MActor) { | ||
26 | const video = videoArg as MVideoAP | ||
18 | 27 | ||
19 | async function sendUpdateVideo (video: VideoModel, t: Transaction, overrodeByActor?: ActorModel) { | ||
20 | if (video.privacy === VideoPrivacy.PRIVATE) return undefined | 28 | if (video.privacy === VideoPrivacy.PRIVATE) return undefined |
21 | 29 | ||
22 | logger.info('Creating job to update video %s.', video.url) | 30 | logger.info('Creating job to update video %s.', video.url) |
@@ -41,7 +49,7 @@ async function sendUpdateVideo (video: VideoModel, t: Transaction, overrodeByAct | |||
41 | return broadcastToFollowers(updateActivity, byActor, actorsInvolved, t) | 49 | return broadcastToFollowers(updateActivity, byActor, actorsInvolved, t) |
42 | } | 50 | } |
43 | 51 | ||
44 | async function sendUpdateActor (accountOrChannel: AccountModel | VideoChannelModel, t: Transaction) { | 52 | async function sendUpdateActor (accountOrChannel: MChannelDefault | MAccountDefault, t: Transaction) { |
45 | const byActor = accountOrChannel.Actor | 53 | const byActor = accountOrChannel.Actor |
46 | 54 | ||
47 | logger.info('Creating job to update actor %s.', byActor.url) | 55 | logger.info('Creating job to update actor %s.', byActor.url) |
@@ -51,7 +59,7 @@ async function sendUpdateActor (accountOrChannel: AccountModel | VideoChannelMod | |||
51 | const audience = getAudience(byActor) | 59 | const audience = getAudience(byActor) |
52 | const updateActivity = buildUpdateActivity(url, byActor, accountOrChannelObject, audience) | 60 | const updateActivity = buildUpdateActivity(url, byActor, accountOrChannelObject, audience) |
53 | 61 | ||
54 | let actorsInvolved: ActorModel[] | 62 | let actorsInvolved: MActor[] |
55 | if (accountOrChannel instanceof AccountModel) { | 63 | if (accountOrChannel instanceof AccountModel) { |
56 | // Actors that shared my videos are involved too | 64 | // Actors that shared my videos are involved too |
57 | actorsInvolved = await VideoShareModel.loadActorsWhoSharedVideosOf(byActor.id, t) | 65 | actorsInvolved = await VideoShareModel.loadActorsWhoSharedVideosOf(byActor.id, t) |
@@ -65,7 +73,7 @@ async function sendUpdateActor (accountOrChannel: AccountModel | VideoChannelMod | |||
65 | return broadcastToFollowers(updateActivity, byActor, actorsInvolved, t) | 73 | return broadcastToFollowers(updateActivity, byActor, actorsInvolved, t) |
66 | } | 74 | } |
67 | 75 | ||
68 | async function sendUpdateCacheFile (byActor: ActorModel, redundancyModel: VideoRedundancyModel) { | 76 | async function sendUpdateCacheFile (byActor: MActorLight, redundancyModel: MVideoRedundancyVideo) { |
69 | logger.info('Creating job to update cache file %s.', redundancyModel.url) | 77 | logger.info('Creating job to update cache file %s.', redundancyModel.url) |
70 | 78 | ||
71 | const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(redundancyModel.getVideo().id) | 79 | const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(redundancyModel.getVideo().id) |
@@ -80,7 +88,7 @@ async function sendUpdateCacheFile (byActor: ActorModel, redundancyModel: VideoR | |||
80 | return sendVideoRelatedActivity(activityBuilder, { byActor, video }) | 88 | return sendVideoRelatedActivity(activityBuilder, { byActor, video }) |
81 | } | 89 | } |
82 | 90 | ||
83 | async function sendUpdateVideoPlaylist (videoPlaylist: VideoPlaylistModel, t: Transaction) { | 91 | async function sendUpdateVideoPlaylist (videoPlaylist: MVideoPlaylistFull, t: Transaction) { |
84 | if (videoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) return undefined | 92 | if (videoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) return undefined |
85 | 93 | ||
86 | const byActor = videoPlaylist.OwnerAccount.Actor | 94 | const byActor = videoPlaylist.OwnerAccount.Actor |
@@ -113,7 +121,7 @@ export { | |||
113 | 121 | ||
114 | // --------------------------------------------------------------------------- | 122 | // --------------------------------------------------------------------------- |
115 | 123 | ||
116 | function buildUpdateActivity (url: string, byActor: ActorModel, object: any, audience?: ActivityAudience): ActivityUpdate { | 124 | function buildUpdateActivity (url: string, byActor: MActorLight, object: any, audience?: ActivityAudience): ActivityUpdate { |
117 | if (!audience) audience = getAudience(byActor) | 125 | if (!audience) audience = getAudience(byActor) |
118 | 126 | ||
119 | return audiencify( | 127 | return audiencify( |
@@ -121,8 +129,7 @@ function buildUpdateActivity (url: string, byActor: ActorModel, object: any, aud | |||
121 | type: 'Update' as 'Update', | 129 | type: 'Update' as 'Update', |
122 | id: url, | 130 | id: url, |
123 | actor: byActor.url, | 131 | actor: byActor.url, |
124 | object: audiencify(object, audience | 132 | object: audiencify(object, audience) |
125 | ) | ||
126 | }, | 133 | }, |
127 | audience | 134 | audience |
128 | ) | 135 | ) |
diff --git a/server/lib/activitypub/send/send-view.ts b/server/lib/activitypub/send/send-view.ts index 8ad126be0..8809417f9 100644 --- a/server/lib/activitypub/send/send-view.ts +++ b/server/lib/activitypub/send/send-view.ts | |||
@@ -1,13 +1,13 @@ | |||
1 | import { Transaction } from 'sequelize' | 1 | import { Transaction } from 'sequelize' |
2 | import { ActivityAudience, ActivityView } from '../../../../shared/models/activitypub' | 2 | import { ActivityAudience, ActivityView } from '../../../../shared/models/activitypub' |
3 | import { ActorModel } from '../../../models/activitypub/actor' | 3 | import { ActorModel } from '../../../models/activitypub/actor' |
4 | import { VideoModel } from '../../../models/video/video' | ||
5 | import { getVideoLikeActivityPubUrl } from '../url' | 4 | import { getVideoLikeActivityPubUrl } from '../url' |
6 | import { sendVideoRelatedActivity } from './utils' | 5 | import { sendVideoRelatedActivity } from './utils' |
7 | import { audiencify, getAudience } from '../audience' | 6 | import { audiencify, getAudience } from '../audience' |
8 | import { logger } from '../../../helpers/logger' | 7 | import { logger } from '../../../helpers/logger' |
8 | import { MActorAudience, MVideoAccountLight, MVideoUrl } from '@server/typings/models' | ||
9 | 9 | ||
10 | async function sendView (byActor: ActorModel, video: VideoModel, t: Transaction) { | 10 | async function sendView (byActor: ActorModel, video: MVideoAccountLight, t: Transaction) { |
11 | logger.info('Creating job to send view of %s.', video.url) | 11 | logger.info('Creating job to send view of %s.', video.url) |
12 | 12 | ||
13 | const activityBuilder = (audience: ActivityAudience) => { | 13 | const activityBuilder = (audience: ActivityAudience) => { |
@@ -19,7 +19,7 @@ async function sendView (byActor: ActorModel, video: VideoModel, t: Transaction) | |||
19 | return sendVideoRelatedActivity(activityBuilder, { byActor, video, transaction: t }) | 19 | return sendVideoRelatedActivity(activityBuilder, { byActor, video, transaction: t }) |
20 | } | 20 | } |
21 | 21 | ||
22 | function buildViewActivity (url: string, byActor: ActorModel, video: VideoModel, audience?: ActivityAudience): ActivityView { | 22 | function buildViewActivity (url: string, byActor: MActorAudience, video: MVideoUrl, audience?: ActivityAudience): ActivityView { |
23 | if (!audience) audience = getAudience(byActor) | 23 | if (!audience) audience = getAudience(byActor) |
24 | 24 | ||
25 | return audiencify( | 25 | return audiencify( |
diff --git a/server/lib/activitypub/send/utils.ts b/server/lib/activitypub/send/utils.ts index 4f69afb00..8129ab32a 100644 --- a/server/lib/activitypub/send/utils.ts +++ b/server/lib/activitypub/send/utils.ts | |||
@@ -4,15 +4,14 @@ import { logger } from '../../../helpers/logger' | |||
4 | import { ActorModel } from '../../../models/activitypub/actor' | 4 | import { ActorModel } from '../../../models/activitypub/actor' |
5 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' | 5 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' |
6 | import { JobQueue } from '../../job-queue' | 6 | import { JobQueue } from '../../job-queue' |
7 | import { VideoModel } from '../../../models/video/video' | ||
8 | import { getActorsInvolvedInVideo, getAudienceFromFollowersOf, getRemoteVideoAudience } from '../audience' | 7 | import { getActorsInvolvedInVideo, getAudienceFromFollowersOf, getRemoteVideoAudience } from '../audience' |
9 | import { getServerActor } from '../../../helpers/utils' | 8 | import { getServerActor } from '../../../helpers/utils' |
10 | import { afterCommitIfTransaction } from '../../../helpers/database-utils' | 9 | import { afterCommitIfTransaction } from '../../../helpers/database-utils' |
11 | import { ActorFollowerException, ActorModelId, ActorModelOnly } from '../../../typings/models' | 10 | import { MActorFollowerException, MActor, MActorId, MActorLight, MVideo, MVideoAccountLight } from '../../../typings/models' |
12 | 11 | ||
13 | async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAudience) => Activity, options: { | 12 | async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAudience) => Activity, options: { |
14 | byActor: ActorModelOnly, | 13 | byActor: MActorLight, |
15 | video: VideoModel, | 14 | video: MVideoAccountLight, |
16 | transaction?: Transaction | 15 | transaction?: Transaction |
17 | }) { | 16 | }) { |
18 | const { byActor, video, transaction } = options | 17 | const { byActor, video, transaction } = options |
@@ -41,8 +40,8 @@ async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAud | |||
41 | async function forwardVideoRelatedActivity ( | 40 | async function forwardVideoRelatedActivity ( |
42 | activity: Activity, | 41 | activity: Activity, |
43 | t: Transaction, | 42 | t: Transaction, |
44 | followersException: ActorFollowerException[] = [], | 43 | followersException: MActorFollowerException[] = [], |
45 | video: VideoModel | 44 | video: MVideo |
46 | ) { | 45 | ) { |
47 | // Mastodon does not add our announces in audience, so we forward to them manually | 46 | // Mastodon does not add our announces in audience, so we forward to them manually |
48 | const additionalActors = await getActorsInvolvedInVideo(video, t) | 47 | const additionalActors = await getActorsInvolvedInVideo(video, t) |
@@ -54,7 +53,7 @@ async function forwardVideoRelatedActivity ( | |||
54 | async function forwardActivity ( | 53 | async function forwardActivity ( |
55 | activity: Activity, | 54 | activity: Activity, |
56 | t: Transaction, | 55 | t: Transaction, |
57 | followersException: ActorFollowerException[] = [], | 56 | followersException: MActorFollowerException[] = [], |
58 | additionalFollowerUrls: string[] = [] | 57 | additionalFollowerUrls: string[] = [] |
59 | ) { | 58 | ) { |
60 | logger.info('Forwarding activity %s.', activity.id) | 59 | logger.info('Forwarding activity %s.', activity.id) |
@@ -88,10 +87,10 @@ async function forwardActivity ( | |||
88 | 87 | ||
89 | async function broadcastToFollowers ( | 88 | async function broadcastToFollowers ( |
90 | data: any, | 89 | data: any, |
91 | byActor: ActorModelId, | 90 | byActor: MActorId, |
92 | toFollowersOf: ActorModelId[], | 91 | toFollowersOf: MActorId[], |
93 | t: Transaction, | 92 | t: Transaction, |
94 | actorsException: ActorFollowerException[] = [] | 93 | actorsException: MActorFollowerException[] = [] |
95 | ) { | 94 | ) { |
96 | const uris = await computeFollowerUris(toFollowersOf, actorsException, t) | 95 | const uris = await computeFollowerUris(toFollowersOf, actorsException, t) |
97 | 96 | ||
@@ -100,16 +99,16 @@ async function broadcastToFollowers ( | |||
100 | 99 | ||
101 | async function broadcastToActors ( | 100 | async function broadcastToActors ( |
102 | data: any, | 101 | data: any, |
103 | byActor: ActorModelId, | 102 | byActor: MActorId, |
104 | toActors: ActorModelOnly[], | 103 | toActors: MActor[], |
105 | t?: Transaction, | 104 | t?: Transaction, |
106 | actorsException: ActorFollowerException[] = [] | 105 | actorsException: MActorFollowerException[] = [] |
107 | ) { | 106 | ) { |
108 | const uris = await computeUris(toActors, actorsException) | 107 | const uris = await computeUris(toActors, actorsException) |
109 | return afterCommitIfTransaction(t, () => broadcastTo(uris, data, byActor)) | 108 | return afterCommitIfTransaction(t, () => broadcastTo(uris, data, byActor)) |
110 | } | 109 | } |
111 | 110 | ||
112 | function broadcastTo (uris: string[], data: any, byActor: ActorModelId) { | 111 | function broadcastTo (uris: string[], data: any, byActor: MActorId) { |
113 | if (uris.length === 0) return undefined | 112 | if (uris.length === 0) return undefined |
114 | 113 | ||
115 | logger.debug('Creating broadcast job.', { uris }) | 114 | logger.debug('Creating broadcast job.', { uris }) |
@@ -123,7 +122,7 @@ function broadcastTo (uris: string[], data: any, byActor: ActorModelId) { | |||
123 | return JobQueue.Instance.createJob({ type: 'activitypub-http-broadcast', payload }) | 122 | return JobQueue.Instance.createJob({ type: 'activitypub-http-broadcast', payload }) |
124 | } | 123 | } |
125 | 124 | ||
126 | function unicastTo (data: any, byActor: ActorModelId, toActorUrl: string) { | 125 | function unicastTo (data: any, byActor: MActorId, toActorUrl: string) { |
127 | logger.debug('Creating unicast job.', { uri: toActorUrl }) | 126 | logger.debug('Creating unicast job.', { uri: toActorUrl }) |
128 | 127 | ||
129 | const payload = { | 128 | const payload = { |
@@ -148,7 +147,7 @@ export { | |||
148 | 147 | ||
149 | // --------------------------------------------------------------------------- | 148 | // --------------------------------------------------------------------------- |
150 | 149 | ||
151 | async function computeFollowerUris (toFollowersOf: ActorModelId[], actorsException: ActorFollowerException[], t: Transaction) { | 150 | async function computeFollowerUris (toFollowersOf: MActorId[], actorsException: MActorFollowerException[], t: Transaction) { |
152 | const toActorFollowerIds = toFollowersOf.map(a => a.id) | 151 | const toActorFollowerIds = toFollowersOf.map(a => a.id) |
153 | 152 | ||
154 | const result = await ActorFollowModel.listAcceptedFollowerSharedInboxUrls(toActorFollowerIds, t) | 153 | const result = await ActorFollowModel.listAcceptedFollowerSharedInboxUrls(toActorFollowerIds, t) |
@@ -157,7 +156,7 @@ async function computeFollowerUris (toFollowersOf: ActorModelId[], actorsExcepti | |||
157 | return result.data.filter(sharedInbox => sharedInboxesException.indexOf(sharedInbox) === -1) | 156 | return result.data.filter(sharedInbox => sharedInboxesException.indexOf(sharedInbox) === -1) |
158 | } | 157 | } |
159 | 158 | ||
160 | async function computeUris (toActors: ActorModelOnly[], actorsException: ActorFollowerException[] = []) { | 159 | async function computeUris (toActors: MActor[], actorsException: MActorFollowerException[] = []) { |
161 | const serverActor = await getServerActor() | 160 | const serverActor = await getServerActor() |
162 | const targetUrls = toActors | 161 | const targetUrls = toActors |
163 | .filter(a => a.id !== serverActor.id) // Don't send to ourselves | 162 | .filter(a => a.id !== serverActor.id) // Don't send to ourselves |
@@ -170,7 +169,7 @@ async function computeUris (toActors: ActorModelOnly[], actorsException: ActorFo | |||
170 | .filter(sharedInbox => sharedInboxesException.indexOf(sharedInbox) === -1) | 169 | .filter(sharedInbox => sharedInboxesException.indexOf(sharedInbox) === -1) |
171 | } | 170 | } |
172 | 171 | ||
173 | async function buildSharedInboxesException (actorsException: ActorFollowerException[]) { | 172 | async function buildSharedInboxesException (actorsException: MActorFollowerException[]) { |
174 | const serverActor = await getServerActor() | 173 | const serverActor = await getServerActor() |
175 | 174 | ||
176 | return actorsException | 175 | return actorsException |
diff --git a/server/lib/activitypub/share.ts b/server/lib/activitypub/share.ts index 7f38402b6..fdca9bed7 100644 --- a/server/lib/activitypub/share.ts +++ b/server/lib/activitypub/share.ts | |||
@@ -1,19 +1,18 @@ | |||
1 | import { Transaction } from 'sequelize' | 1 | import { Transaction } from 'sequelize' |
2 | import { VideoPrivacy } from '../../../shared/models/videos' | 2 | import { VideoPrivacy } from '../../../shared/models/videos' |
3 | import { getServerActor } from '../../helpers/utils' | 3 | import { getServerActor } from '../../helpers/utils' |
4 | import { VideoModel } from '../../models/video/video' | ||
5 | import { VideoShareModel } from '../../models/video/video-share' | 4 | import { VideoShareModel } from '../../models/video/video-share' |
6 | import { sendUndoAnnounce, sendVideoAnnounce } from './send' | 5 | import { sendUndoAnnounce, sendVideoAnnounce } from './send' |
7 | import { getVideoAnnounceActivityPubUrl } from './url' | 6 | import { getVideoAnnounceActivityPubUrl } from './url' |
8 | import { VideoChannelModel } from '../../models/video/video-channel' | ||
9 | import * as Bluebird from 'bluebird' | 7 | import * as Bluebird from 'bluebird' |
10 | import { doRequest } from '../../helpers/requests' | 8 | import { doRequest } from '../../helpers/requests' |
11 | import { getOrCreateActorAndServerAndModel } from './actor' | 9 | import { getOrCreateActorAndServerAndModel } from './actor' |
12 | import { logger } from '../../helpers/logger' | 10 | import { logger } from '../../helpers/logger' |
13 | import { CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants' | 11 | import { CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants' |
14 | import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub' | 12 | import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub' |
13 | import { MChannelActor, MChannelActorLight, MVideo, MVideoAccountLight, MVideoId } from '../../typings/models/video' | ||
15 | 14 | ||
16 | async function shareVideoByServerAndChannel (video: VideoModel, t: Transaction) { | 15 | async function shareVideoByServerAndChannel (video: MVideoAccountLight, t: Transaction) { |
17 | if (video.privacy === VideoPrivacy.PRIVATE) return undefined | 16 | if (video.privacy === VideoPrivacy.PRIVATE) return undefined |
18 | 17 | ||
19 | return Promise.all([ | 18 | return Promise.all([ |
@@ -22,7 +21,11 @@ async function shareVideoByServerAndChannel (video: VideoModel, t: Transaction) | |||
22 | ]) | 21 | ]) |
23 | } | 22 | } |
24 | 23 | ||
25 | async function changeVideoChannelShare (video: VideoModel, oldVideoChannel: VideoChannelModel, t: Transaction) { | 24 | async function changeVideoChannelShare ( |
25 | video: MVideoAccountLight, | ||
26 | oldVideoChannel: MChannelActorLight, | ||
27 | t: Transaction | ||
28 | ) { | ||
26 | logger.info('Updating video channel of video %s: %s -> %s.', video.uuid, oldVideoChannel.name, video.VideoChannel.name) | 29 | logger.info('Updating video channel of video %s: %s -> %s.', video.uuid, oldVideoChannel.name, video.VideoChannel.name) |
27 | 30 | ||
28 | await undoShareByVideoChannel(video, oldVideoChannel, t) | 31 | await undoShareByVideoChannel(video, oldVideoChannel, t) |
@@ -30,7 +33,7 @@ async function changeVideoChannelShare (video: VideoModel, oldVideoChannel: Vide | |||
30 | await shareByVideoChannel(video, t) | 33 | await shareByVideoChannel(video, t) |
31 | } | 34 | } |
32 | 35 | ||
33 | async function addVideoShares (shareUrls: string[], instance: VideoModel) { | 36 | async function addVideoShares (shareUrls: string[], video: MVideoId) { |
34 | await Bluebird.map(shareUrls, async shareUrl => { | 37 | await Bluebird.map(shareUrls, async shareUrl => { |
35 | try { | 38 | try { |
36 | // Fetch url | 39 | // Fetch url |
@@ -50,7 +53,7 @@ async function addVideoShares (shareUrls: string[], instance: VideoModel) { | |||
50 | 53 | ||
51 | const entry = { | 54 | const entry = { |
52 | actorId: actor.id, | 55 | actorId: actor.id, |
53 | videoId: instance.id, | 56 | videoId: video.id, |
54 | url: shareUrl | 57 | url: shareUrl |
55 | } | 58 | } |
56 | 59 | ||
@@ -69,7 +72,7 @@ export { | |||
69 | 72 | ||
70 | // --------------------------------------------------------------------------- | 73 | // --------------------------------------------------------------------------- |
71 | 74 | ||
72 | async function shareByServer (video: VideoModel, t: Transaction) { | 75 | async function shareByServer (video: MVideo, t: Transaction) { |
73 | const serverActor = await getServerActor() | 76 | const serverActor = await getServerActor() |
74 | 77 | ||
75 | const serverShareUrl = getVideoAnnounceActivityPubUrl(serverActor, video) | 78 | const serverShareUrl = getVideoAnnounceActivityPubUrl(serverActor, video) |
@@ -88,7 +91,7 @@ async function shareByServer (video: VideoModel, t: Transaction) { | |||
88 | return sendVideoAnnounce(serverActor, serverShare, video, t) | 91 | return sendVideoAnnounce(serverActor, serverShare, video, t) |
89 | } | 92 | } |
90 | 93 | ||
91 | async function shareByVideoChannel (video: VideoModel, t: Transaction) { | 94 | async function shareByVideoChannel (video: MVideoAccountLight, t: Transaction) { |
92 | const videoChannelShareUrl = getVideoAnnounceActivityPubUrl(video.VideoChannel.Actor, video) | 95 | const videoChannelShareUrl = getVideoAnnounceActivityPubUrl(video.VideoChannel.Actor, video) |
93 | const [ videoChannelShare ] = await VideoShareModel.findOrCreate({ | 96 | const [ videoChannelShare ] = await VideoShareModel.findOrCreate({ |
94 | defaults: { | 97 | defaults: { |
@@ -105,7 +108,7 @@ async function shareByVideoChannel (video: VideoModel, t: Transaction) { | |||
105 | return sendVideoAnnounce(video.VideoChannel.Actor, videoChannelShare, video, t) | 108 | return sendVideoAnnounce(video.VideoChannel.Actor, videoChannelShare, video, t) |
106 | } | 109 | } |
107 | 110 | ||
108 | async function undoShareByVideoChannel (video: VideoModel, oldVideoChannel: VideoChannelModel, t: Transaction) { | 111 | async function undoShareByVideoChannel (video: MVideo, oldVideoChannel: MChannelActorLight, t: Transaction) { |
109 | // Load old share | 112 | // Load old share |
110 | const oldShare = await VideoShareModel.load(oldVideoChannel.actorId, video.id, t) | 113 | const oldShare = await VideoShareModel.load(oldVideoChannel.actorId, video.id, t) |
111 | if (!oldShare) return new Error('Cannot find old video channel share ' + oldVideoChannel.actorId + ' for video ' + video.id) | 114 | if (!oldShare) return new Error('Cannot find old video channel share ' + oldVideoChannel.actorId + ' for video ' + video.id) |
diff --git a/server/lib/activitypub/url.ts b/server/lib/activitypub/url.ts index dfcb3c668..6290af34b 100644 --- a/server/lib/activitypub/url.ts +++ b/server/lib/activitypub/url.ts | |||
@@ -1,36 +1,42 @@ | |||
1 | import { WEBSERVER } from '../../initializers/constants' | 1 | import { WEBSERVER } from '../../initializers/constants' |
2 | import { VideoModel } from '../../models/video/video' | 2 | import { |
3 | import { VideoAbuseModel } from '../../models/video/video-abuse' | 3 | MActor, |
4 | import { VideoCommentModel } from '../../models/video/video-comment' | 4 | MActorFollowActors, |
5 | import { VideoFileModel } from '../../models/video/video-file' | 5 | MActorId, |
6 | import { VideoStreamingPlaylistModel } from '../../models/video/video-streaming-playlist' | 6 | MActorUrl, |
7 | import { VideoPlaylistModel } from '../../models/video/video-playlist' | 7 | MCommentId, |
8 | import { ActorModelOnly, ActorModelUrl } from '../../typings/models' | 8 | MVideoAbuseId, |
9 | import { ActorFollowModelLight } from '../../typings/models/actor-follow' | 9 | MVideoId, |
10 | 10 | MVideoUrl, | |
11 | function getVideoActivityPubUrl (video: VideoModel) { | 11 | MVideoUUID |
12 | } from '../../typings/models' | ||
13 | import { MVideoPlaylist, MVideoPlaylistUUID } from '../../typings/models/video/video-playlist' | ||
14 | import { MVideoFileVideoUUID } from '../../typings/models/video/video-file' | ||
15 | import { MStreamingPlaylist } from '../../typings/models/video/video-streaming-playlist' | ||
16 | |||
17 | function getVideoActivityPubUrl (video: MVideoUUID) { | ||
12 | return WEBSERVER.URL + '/videos/watch/' + video.uuid | 18 | return WEBSERVER.URL + '/videos/watch/' + video.uuid |
13 | } | 19 | } |
14 | 20 | ||
15 | function getVideoPlaylistActivityPubUrl (videoPlaylist: VideoPlaylistModel) { | 21 | function getVideoPlaylistActivityPubUrl (videoPlaylist: MVideoPlaylist) { |
16 | return WEBSERVER.URL + '/video-playlists/' + videoPlaylist.uuid | 22 | return WEBSERVER.URL + '/video-playlists/' + videoPlaylist.uuid |
17 | } | 23 | } |
18 | 24 | ||
19 | function getVideoPlaylistElementActivityPubUrl (videoPlaylist: VideoPlaylistModel, video: VideoModel) { | 25 | function getVideoPlaylistElementActivityPubUrl (videoPlaylist: MVideoPlaylistUUID, video: MVideoUUID) { |
20 | return WEBSERVER.URL + '/video-playlists/' + videoPlaylist.uuid + '/' + video.uuid | 26 | return WEBSERVER.URL + '/video-playlists/' + videoPlaylist.uuid + '/' + video.uuid |
21 | } | 27 | } |
22 | 28 | ||
23 | function getVideoCacheFileActivityPubUrl (videoFile: VideoFileModel) { | 29 | function getVideoCacheFileActivityPubUrl (videoFile: MVideoFileVideoUUID) { |
24 | const suffixFPS = videoFile.fps && videoFile.fps !== -1 ? '-' + videoFile.fps : '' | 30 | const suffixFPS = videoFile.fps && videoFile.fps !== -1 ? '-' + videoFile.fps : '' |
25 | 31 | ||
26 | return `${WEBSERVER.URL}/redundancy/videos/${videoFile.Video.uuid}/${videoFile.resolution}${suffixFPS}` | 32 | return `${WEBSERVER.URL}/redundancy/videos/${videoFile.Video.uuid}/${videoFile.resolution}${suffixFPS}` |
27 | } | 33 | } |
28 | 34 | ||
29 | function getVideoCacheStreamingPlaylistActivityPubUrl (video: VideoModel, playlist: VideoStreamingPlaylistModel) { | 35 | function getVideoCacheStreamingPlaylistActivityPubUrl (video: MVideoUUID, playlist: MStreamingPlaylist) { |
30 | return `${WEBSERVER.URL}/redundancy/streaming-playlists/${playlist.getStringType()}/${video.uuid}` | 36 | return `${WEBSERVER.URL}/redundancy/streaming-playlists/${playlist.getStringType()}/${video.uuid}` |
31 | } | 37 | } |
32 | 38 | ||
33 | function getVideoCommentActivityPubUrl (video: VideoModel, videoComment: VideoCommentModel) { | 39 | function getVideoCommentActivityPubUrl (video: MVideoUUID, videoComment: MCommentId) { |
34 | return WEBSERVER.URL + '/videos/watch/' + video.uuid + '/comments/' + videoComment.id | 40 | return WEBSERVER.URL + '/videos/watch/' + video.uuid + '/comments/' + videoComment.id |
35 | } | 41 | } |
36 | 42 | ||
@@ -42,54 +48,54 @@ function getAccountActivityPubUrl (accountName: string) { | |||
42 | return WEBSERVER.URL + '/accounts/' + accountName | 48 | return WEBSERVER.URL + '/accounts/' + accountName |
43 | } | 49 | } |
44 | 50 | ||
45 | function getVideoAbuseActivityPubUrl (videoAbuse: VideoAbuseModel) { | 51 | function getVideoAbuseActivityPubUrl (videoAbuse: MVideoAbuseId) { |
46 | return WEBSERVER.URL + '/admin/video-abuses/' + videoAbuse.id | 52 | return WEBSERVER.URL + '/admin/video-abuses/' + videoAbuse.id |
47 | } | 53 | } |
48 | 54 | ||
49 | function getVideoViewActivityPubUrl (byActor: ActorModelUrl, video: VideoModel) { | 55 | function getVideoViewActivityPubUrl (byActor: MActorUrl, video: MVideoId) { |
50 | return byActor.url + '/views/videos/' + video.id + '/' + new Date().toISOString() | 56 | return byActor.url + '/views/videos/' + video.id + '/' + new Date().toISOString() |
51 | } | 57 | } |
52 | 58 | ||
53 | function getVideoLikeActivityPubUrl (byActor: ActorModelUrl, video: VideoModel | { id: number }) { | 59 | function getVideoLikeActivityPubUrl (byActor: MActorUrl, video: MVideoId) { |
54 | return byActor.url + '/likes/' + video.id | 60 | return byActor.url + '/likes/' + video.id |
55 | } | 61 | } |
56 | 62 | ||
57 | function getVideoDislikeActivityPubUrl (byActor: ActorModelUrl, video: VideoModel | { id: number }) { | 63 | function getVideoDislikeActivityPubUrl (byActor: MActorUrl, video: MVideoId) { |
58 | return byActor.url + '/dislikes/' + video.id | 64 | return byActor.url + '/dislikes/' + video.id |
59 | } | 65 | } |
60 | 66 | ||
61 | function getVideoSharesActivityPubUrl (video: VideoModel) { | 67 | function getVideoSharesActivityPubUrl (video: MVideoUrl) { |
62 | return video.url + '/announces' | 68 | return video.url + '/announces' |
63 | } | 69 | } |
64 | 70 | ||
65 | function getVideoCommentsActivityPubUrl (video: VideoModel) { | 71 | function getVideoCommentsActivityPubUrl (video: MVideoUrl) { |
66 | return video.url + '/comments' | 72 | return video.url + '/comments' |
67 | } | 73 | } |
68 | 74 | ||
69 | function getVideoLikesActivityPubUrl (video: VideoModel) { | 75 | function getVideoLikesActivityPubUrl (video: MVideoUrl) { |
70 | return video.url + '/likes' | 76 | return video.url + '/likes' |
71 | } | 77 | } |
72 | 78 | ||
73 | function getVideoDislikesActivityPubUrl (video: VideoModel) { | 79 | function getVideoDislikesActivityPubUrl (video: MVideoUrl) { |
74 | return video.url + '/dislikes' | 80 | return video.url + '/dislikes' |
75 | } | 81 | } |
76 | 82 | ||
77 | function getActorFollowActivityPubUrl (follower: ActorModelOnly, following: ActorModelOnly) { | 83 | function getActorFollowActivityPubUrl (follower: MActor, following: MActorId) { |
78 | return follower.url + '/follows/' + following.id | 84 | return follower.url + '/follows/' + following.id |
79 | } | 85 | } |
80 | 86 | ||
81 | function getActorFollowAcceptActivityPubUrl (actorFollow: ActorFollowModelLight) { | 87 | function getActorFollowAcceptActivityPubUrl (actorFollow: MActorFollowActors) { |
82 | const follower = actorFollow.ActorFollower | 88 | const follower = actorFollow.ActorFollower |
83 | const me = actorFollow.ActorFollowing | 89 | const me = actorFollow.ActorFollowing |
84 | 90 | ||
85 | return follower.url + '/accepts/follows/' + me.id | 91 | return follower.url + '/accepts/follows/' + me.id |
86 | } | 92 | } |
87 | 93 | ||
88 | function getActorFollowRejectActivityPubUrl (follower: ActorModelOnly, following: ActorModelOnly) { | 94 | function getActorFollowRejectActivityPubUrl (follower: MActorUrl, following: MActorId) { |
89 | return follower.url + '/rejects/follows/' + following.id | 95 | return follower.url + '/rejects/follows/' + following.id |
90 | } | 96 | } |
91 | 97 | ||
92 | function getVideoAnnounceActivityPubUrl (byActor: ActorModelOnly, video: VideoModel) { | 98 | function getVideoAnnounceActivityPubUrl (byActor: MActorId, video: MVideoUrl) { |
93 | return video.url + '/announces/' + byActor.id | 99 | return video.url + '/announces/' + byActor.id |
94 | } | 100 | } |
95 | 101 | ||
diff --git a/server/lib/activitypub/video-comments.ts b/server/lib/activitypub/video-comments.ts index 8d2c1ade3..3e8306fa4 100644 --- a/server/lib/activitypub/video-comments.ts +++ b/server/lib/activitypub/video-comments.ts | |||
@@ -2,20 +2,20 @@ import { sanitizeAndCheckVideoCommentObject } from '../../helpers/custom-validat | |||
2 | import { logger } from '../../helpers/logger' | 2 | import { logger } from '../../helpers/logger' |
3 | import { doRequest } from '../../helpers/requests' | 3 | import { doRequest } from '../../helpers/requests' |
4 | import { ACTIVITY_PUB, CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants' | 4 | import { ACTIVITY_PUB, CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants' |
5 | import { VideoModel } from '../../models/video/video' | ||
6 | import { VideoCommentModel } from '../../models/video/video-comment' | 5 | import { VideoCommentModel } from '../../models/video/video-comment' |
7 | import { getOrCreateActorAndServerAndModel } from './actor' | 6 | import { getOrCreateActorAndServerAndModel } from './actor' |
8 | import { getOrCreateVideoAndAccountAndChannel } from './videos' | 7 | import { getOrCreateVideoAndAccountAndChannel } from './videos' |
9 | import * as Bluebird from 'bluebird' | 8 | import * as Bluebird from 'bluebird' |
10 | import { checkUrlsSameHost } from '../../helpers/activitypub' | 9 | import { checkUrlsSameHost } from '../../helpers/activitypub' |
10 | import { MCommentOwner, MCommentOwnerVideo, MVideoAccountLightBlacklistAllFiles } from '../../typings/models/video' | ||
11 | 11 | ||
12 | type ResolveThreadParams = { | 12 | type ResolveThreadParams = { |
13 | url: string, | 13 | url: string, |
14 | comments?: VideoCommentModel[], | 14 | comments?: MCommentOwner[], |
15 | isVideo?: boolean, | 15 | isVideo?: boolean, |
16 | commentCreated?: boolean | 16 | commentCreated?: boolean |
17 | } | 17 | } |
18 | type ResolveThreadResult = Promise<{ video: VideoModel, comment: VideoCommentModel, commentCreated: boolean }> | 18 | type ResolveThreadResult = Promise<{ video: MVideoAccountLightBlacklistAllFiles, comment: MCommentOwnerVideo, commentCreated: boolean }> |
19 | 19 | ||
20 | async function addVideoComments (commentUrls: string[]) { | 20 | async function addVideoComments (commentUrls: string[]) { |
21 | return Bluebird.map(commentUrls, commentUrl => { | 21 | return Bluebird.map(commentUrls, commentUrl => { |
@@ -85,9 +85,9 @@ async function tryResolveThreadFromVideo (params: ResolveThreadParams) { | |||
85 | const syncParam = { likes: true, dislikes: true, shares: true, comments: false, thumbnail: true, refreshVideo: false } | 85 | const syncParam = { likes: true, dislikes: true, shares: true, comments: false, thumbnail: true, refreshVideo: false } |
86 | const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: url, syncParam }) | 86 | const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: url, syncParam }) |
87 | 87 | ||
88 | let resultComment: VideoCommentModel | 88 | let resultComment: MCommentOwnerVideo |
89 | if (comments.length !== 0) { | 89 | if (comments.length !== 0) { |
90 | const firstReply = comments[ comments.length - 1 ] | 90 | const firstReply = comments[ comments.length - 1 ] as MCommentOwnerVideo |
91 | firstReply.inReplyToCommentId = null | 91 | firstReply.inReplyToCommentId = null |
92 | firstReply.originCommentId = null | 92 | firstReply.originCommentId = null |
93 | firstReply.videoId = video.id | 93 | firstReply.videoId = video.id |
@@ -97,7 +97,7 @@ async function tryResolveThreadFromVideo (params: ResolveThreadParams) { | |||
97 | comments[comments.length - 1] = await firstReply.save() | 97 | comments[comments.length - 1] = await firstReply.save() |
98 | 98 | ||
99 | for (let i = comments.length - 2; i >= 0; i--) { | 99 | for (let i = comments.length - 2; i >= 0; i--) { |
100 | const comment = comments[ i ] | 100 | const comment = comments[ i ] as MCommentOwnerVideo |
101 | comment.originCommentId = firstReply.id | 101 | comment.originCommentId = firstReply.id |
102 | comment.inReplyToCommentId = comments[ i + 1 ].id | 102 | comment.inReplyToCommentId = comments[ i + 1 ].id |
103 | comment.videoId = video.id | 103 | comment.videoId = video.id |
@@ -107,7 +107,7 @@ async function tryResolveThreadFromVideo (params: ResolveThreadParams) { | |||
107 | comments[i] = await comment.save() | 107 | comments[i] = await comment.save() |
108 | } | 108 | } |
109 | 109 | ||
110 | resultComment = comments[0] | 110 | resultComment = comments[0] as MCommentOwnerVideo |
111 | } | 111 | } |
112 | 112 | ||
113 | return { video, comment: resultComment, commentCreated } | 113 | return { video, comment: resultComment, commentCreated } |
@@ -151,7 +151,7 @@ async function resolveParentComment (params: ResolveThreadParams) { | |||
151 | originCommentId: null, | 151 | originCommentId: null, |
152 | createdAt: new Date(body.published), | 152 | createdAt: new Date(body.published), |
153 | updatedAt: new Date(body.updated) | 153 | updatedAt: new Date(body.updated) |
154 | }) | 154 | }) as MCommentOwner |
155 | comment.Account = actor.Account | 155 | comment.Account = actor.Account |
156 | 156 | ||
157 | return resolveThread({ | 157 | return resolveThread({ |
diff --git a/server/lib/activitypub/video-rates.ts b/server/lib/activitypub/video-rates.ts index cda5b2981..6bd46bb58 100644 --- a/server/lib/activitypub/video-rates.ts +++ b/server/lib/activitypub/video-rates.ts | |||
@@ -1,6 +1,4 @@ | |||
1 | import { Transaction } from 'sequelize' | 1 | import { Transaction } from 'sequelize' |
2 | import { AccountModel } from '../../models/account/account' | ||
3 | import { VideoModel } from '../../models/video/video' | ||
4 | import { sendLike, sendUndoDislike, sendUndoLike } from './send' | 2 | import { sendLike, sendUndoDislike, sendUndoLike } from './send' |
5 | import { VideoRateType } from '../../../shared/models/videos' | 3 | import { VideoRateType } from '../../../shared/models/videos' |
6 | import * as Bluebird from 'bluebird' | 4 | import * as Bluebird from 'bluebird' |
@@ -10,11 +8,11 @@ import { logger } from '../../helpers/logger' | |||
10 | import { CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants' | 8 | import { CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants' |
11 | import { doRequest } from '../../helpers/requests' | 9 | import { doRequest } from '../../helpers/requests' |
12 | import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub' | 10 | import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub' |
13 | import { ActorModel } from '../../models/activitypub/actor' | ||
14 | import { getVideoDislikeActivityPubUrl, getVideoLikeActivityPubUrl } from './url' | 11 | import { getVideoDislikeActivityPubUrl, getVideoLikeActivityPubUrl } from './url' |
15 | import { sendDislike } from './send/send-dislike' | 12 | import { sendDislike } from './send/send-dislike' |
13 | import { MAccountActor, MActorUrl, MVideo, MVideoAccountLight, MVideoId } from '../../typings/models' | ||
16 | 14 | ||
17 | async function createRates (ratesUrl: string[], video: VideoModel, rate: VideoRateType) { | 15 | async function createRates (ratesUrl: string[], video: MVideo, rate: VideoRateType) { |
18 | let rateCounts = 0 | 16 | let rateCounts = 0 |
19 | 17 | ||
20 | await Bluebird.map(ratesUrl, async rateUrl => { | 18 | await Bluebird.map(ratesUrl, async rateUrl => { |
@@ -64,11 +62,13 @@ async function createRates (ratesUrl: string[], video: VideoModel, rate: VideoRa | |||
64 | return | 62 | return |
65 | } | 63 | } |
66 | 64 | ||
67 | async function sendVideoRateChange (account: AccountModel, | 65 | async function sendVideoRateChange ( |
68 | video: VideoModel, | 66 | account: MAccountActor, |
69 | likes: number, | 67 | video: MVideoAccountLight, |
70 | dislikes: number, | 68 | likes: number, |
71 | t: Transaction) { | 69 | dislikes: number, |
70 | t: Transaction | ||
71 | ) { | ||
72 | const actor = account.Actor | 72 | const actor = account.Actor |
73 | 73 | ||
74 | // Keep the order: first we undo and then we create | 74 | // Keep the order: first we undo and then we create |
@@ -84,8 +84,10 @@ async function sendVideoRateChange (account: AccountModel, | |||
84 | if (dislikes > 0) await sendDislike(actor, video, t) | 84 | if (dislikes > 0) await sendDislike(actor, video, t) |
85 | } | 85 | } |
86 | 86 | ||
87 | function getRateUrl (rateType: VideoRateType, actor: ActorModel, video: VideoModel) { | 87 | function getRateUrl (rateType: VideoRateType, actor: MActorUrl, video: MVideoId) { |
88 | return rateType === 'like' ? getVideoLikeActivityPubUrl(actor, video) : getVideoDislikeActivityPubUrl(actor, video) | 88 | return rateType === 'like' |
89 | ? getVideoLikeActivityPubUrl(actor, video) | ||
90 | : getVideoDislikeActivityPubUrl(actor, video) | ||
89 | } | 91 | } |
90 | 92 | ||
91 | export { | 93 | export { |
diff --git a/server/lib/activitypub/videos.ts b/server/lib/activitypub/videos.ts index 3a8451a32..c318978fd 100644 --- a/server/lib/activitypub/videos.ts +++ b/server/lib/activitypub/videos.ts | |||
@@ -24,7 +24,6 @@ import { | |||
24 | REMOTE_SCHEME, | 24 | REMOTE_SCHEME, |
25 | STATIC_PATHS | 25 | STATIC_PATHS |
26 | } from '../../initializers/constants' | 26 | } from '../../initializers/constants' |
27 | import { ActorModel } from '../../models/activitypub/actor' | ||
28 | import { TagModel } from '../../models/video/tag' | 27 | import { TagModel } from '../../models/video/tag' |
29 | import { VideoModel } from '../../models/video/video' | 28 | import { VideoModel } from '../../models/video/video' |
30 | import { VideoFileModel } from '../../models/video/video-file' | 29 | import { VideoFileModel } from '../../models/video/video-file' |
@@ -38,7 +37,6 @@ import { JobQueue } from '../job-queue' | |||
38 | import { ActivitypubHttpFetcherPayload } from '../job-queue/handlers/activitypub-http-fetcher' | 37 | import { ActivitypubHttpFetcherPayload } from '../job-queue/handlers/activitypub-http-fetcher' |
39 | import { createRates } from './video-rates' | 38 | import { createRates } from './video-rates' |
40 | import { addVideoShares, shareVideoByServerAndChannel } from './share' | 39 | import { addVideoShares, shareVideoByServerAndChannel } from './share' |
41 | import { AccountModel } from '../../models/account/account' | ||
42 | import { fetchVideoByUrl, VideoFetchByUrlType } from '../../helpers/video' | 40 | import { fetchVideoByUrl, VideoFetchByUrlType } from '../../helpers/video' |
43 | import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub' | 41 | import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub' |
44 | import { Notifier } from '../notifier' | 42 | import { Notifier } from '../notifier' |
@@ -49,15 +47,31 @@ import { VideoShareModel } from '../../models/video/video-share' | |||
49 | import { VideoCommentModel } from '../../models/video/video-comment' | 47 | import { VideoCommentModel } from '../../models/video/video-comment' |
50 | import { sequelizeTypescript } from '../../initializers/database' | 48 | import { sequelizeTypescript } from '../../initializers/database' |
51 | import { createPlaceholderThumbnail, createVideoMiniatureFromUrl } from '../thumbnail' | 49 | import { createPlaceholderThumbnail, createVideoMiniatureFromUrl } from '../thumbnail' |
52 | import { ThumbnailModel } from '../../models/video/thumbnail' | ||
53 | import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type' | 50 | import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type' |
54 | import { join } from 'path' | 51 | import { join } from 'path' |
55 | import { FilteredModelAttributes } from '../../typings/sequelize' | 52 | import { FilteredModelAttributes } from '../../typings/sequelize' |
56 | import { autoBlacklistVideoIfNeeded } from '../video-blacklist' | 53 | import { autoBlacklistVideoIfNeeded } from '../video-blacklist' |
57 | import { ActorFollowScoreCache } from '../files-cache' | 54 | import { ActorFollowScoreCache } from '../files-cache' |
58 | import { AccountModelIdActor, VideoChannelModelId, VideoChannelModelIdActor } from '../../typings/models' | 55 | import { |
56 | MAccountIdActor, | ||
57 | MChannelAccountLight, | ||
58 | MChannelDefault, | ||
59 | MChannelId, | ||
60 | MVideo, | ||
61 | MVideoAccountLight, | ||
62 | MVideoAccountLightBlacklistAllFiles, | ||
63 | MVideoAP, | ||
64 | MVideoAPWithoutCaption, | ||
65 | MVideoFile, | ||
66 | MVideoFullLight, | ||
67 | MVideoId, | ||
68 | MVideoThumbnail | ||
69 | } from '../../typings/models' | ||
70 | import { MThumbnail } from '../../typings/models/video/thumbnail' | ||
71 | |||
72 | async function federateVideoIfNeeded (videoArg: MVideoAPWithoutCaption, isNewVideo: boolean, transaction?: sequelize.Transaction) { | ||
73 | const video = videoArg as MVideoAP | ||
59 | 74 | ||
60 | async function federateVideoIfNeeded (video: VideoModel, isNewVideo: boolean, transaction?: sequelize.Transaction) { | ||
61 | if ( | 75 | if ( |
62 | // Check this is not a blacklisted video, or unfederated blacklisted video | 76 | // Check this is not a blacklisted video, or unfederated blacklisted video |
63 | (video.isBlacklisted() === false || (isNewVideo === false && video.VideoBlacklist.unfederated === false)) && | 77 | (video.isBlacklisted() === false || (isNewVideo === false && video.VideoBlacklist.unfederated === false)) && |
@@ -102,7 +116,7 @@ async function fetchRemoteVideo (videoUrl: string): Promise<{ response: request. | |||
102 | return { response, videoObject: body } | 116 | return { response, videoObject: body } |
103 | } | 117 | } |
104 | 118 | ||
105 | async function fetchRemoteVideoDescription (video: VideoModel) { | 119 | async function fetchRemoteVideoDescription (video: MVideoAccountLight) { |
106 | const host = video.VideoChannel.Account.Actor.Server.host | 120 | const host = video.VideoChannel.Account.Actor.Server.host |
107 | const path = video.getDescriptionAPIPath() | 121 | const path = video.getDescriptionAPIPath() |
108 | const options = { | 122 | const options = { |
@@ -114,14 +128,14 @@ async function fetchRemoteVideoDescription (video: VideoModel) { | |||
114 | return body.description ? body.description : '' | 128 | return body.description ? body.description : '' |
115 | } | 129 | } |
116 | 130 | ||
117 | function fetchRemoteVideoStaticFile (video: VideoModel, path: string, destPath: string) { | 131 | function fetchRemoteVideoStaticFile (video: MVideoAccountLight, path: string, destPath: string) { |
118 | const url = buildRemoteBaseUrl(video, path) | 132 | const url = buildRemoteBaseUrl(video, path) |
119 | 133 | ||
120 | // We need to provide a callback, if no we could have an uncaught exception | 134 | // We need to provide a callback, if no we could have an uncaught exception |
121 | return doRequestAndSaveToFile({ uri: url }, destPath) | 135 | return doRequestAndSaveToFile({ uri: url }, destPath) |
122 | } | 136 | } |
123 | 137 | ||
124 | function buildRemoteBaseUrl (video: VideoModel, path: string) { | 138 | function buildRemoteBaseUrl (video: MVideoAccountLight, path: string) { |
125 | const host = video.VideoChannel.Account.Actor.Server.host | 139 | const host = video.VideoChannel.Account.Actor.Server.host |
126 | 140 | ||
127 | return REMOTE_SCHEME.HTTP + '://' + host + path | 141 | return REMOTE_SCHEME.HTTP + '://' + host + path |
@@ -146,7 +160,7 @@ type SyncParam = { | |||
146 | thumbnail: boolean | 160 | thumbnail: boolean |
147 | refreshVideo?: boolean | 161 | refreshVideo?: boolean |
148 | } | 162 | } |
149 | async function syncVideoExternalAttributes (video: VideoModel, fetchedVideo: VideoTorrentObject, syncParam: SyncParam) { | 163 | async function syncVideoExternalAttributes (video: MVideo, fetchedVideo: VideoTorrentObject, syncParam: SyncParam) { |
150 | logger.info('Adding likes/dislikes/shares/comments of video %s.', video.uuid) | 164 | logger.info('Adding likes/dislikes/shares/comments of video %s.', video.uuid) |
151 | 165 | ||
152 | const jobPayloads: ActivitypubHttpFetcherPayload[] = [] | 166 | const jobPayloads: ActivitypubHttpFetcherPayload[] = [] |
@@ -194,12 +208,24 @@ async function syncVideoExternalAttributes (video: VideoModel, fetchedVideo: Vid | |||
194 | await Bluebird.map(jobPayloads, payload => JobQueue.Instance.createJob({ type: 'activitypub-http-fetcher', payload })) | 208 | await Bluebird.map(jobPayloads, payload => JobQueue.Instance.createJob({ type: 'activitypub-http-fetcher', payload })) |
195 | } | 209 | } |
196 | 210 | ||
211 | function getOrCreateVideoAndAccountAndChannel (options: { | ||
212 | videoObject: { id: string } | string, | ||
213 | syncParam?: SyncParam, | ||
214 | fetchType?: 'all', | ||
215 | allowRefresh?: boolean | ||
216 | }): Promise<{ video: MVideoAccountLightBlacklistAllFiles, created: boolean, autoBlacklisted?: boolean }> | ||
217 | function getOrCreateVideoAndAccountAndChannel (options: { | ||
218 | videoObject: { id: string } | string, | ||
219 | syncParam?: SyncParam, | ||
220 | fetchType?: VideoFetchByUrlType, | ||
221 | allowRefresh?: boolean | ||
222 | }): Promise<{ video: MVideoAccountLightBlacklistAllFiles | MVideoThumbnail, created: boolean, autoBlacklisted?: boolean }> | ||
197 | async function getOrCreateVideoAndAccountAndChannel (options: { | 223 | async function getOrCreateVideoAndAccountAndChannel (options: { |
198 | videoObject: { id: string } | string, | 224 | videoObject: { id: string } | string, |
199 | syncParam?: SyncParam, | 225 | syncParam?: SyncParam, |
200 | fetchType?: VideoFetchByUrlType, | 226 | fetchType?: VideoFetchByUrlType, |
201 | allowRefresh?: boolean // true by default | 227 | allowRefresh?: boolean // true by default |
202 | }) { | 228 | }): Promise<{ video: MVideoAccountLightBlacklistAllFiles | MVideoThumbnail, created: boolean, autoBlacklisted?: boolean }> { |
203 | // Default params | 229 | // Default params |
204 | const syncParam = options.syncParam || { likes: true, dislikes: true, shares: true, comments: true, thumbnail: true, refreshVideo: false } | 230 | const syncParam = options.syncParam || { likes: true, dislikes: true, shares: true, comments: true, thumbnail: true, refreshVideo: false } |
205 | const fetchType = options.fetchType || 'all' | 231 | const fetchType = options.fetchType || 'all' |
@@ -227,8 +253,9 @@ async function getOrCreateVideoAndAccountAndChannel (options: { | |||
227 | const { videoObject: fetchedVideo } = await fetchRemoteVideo(videoUrl) | 253 | const { videoObject: fetchedVideo } = await fetchRemoteVideo(videoUrl) |
228 | if (!fetchedVideo) throw new Error('Cannot fetch remote video with url: ' + videoUrl) | 254 | if (!fetchedVideo) throw new Error('Cannot fetch remote video with url: ' + videoUrl) |
229 | 255 | ||
230 | const channelActor = await getOrCreateVideoChannelFromVideoObject(fetchedVideo) | 256 | const actor = await getOrCreateVideoChannelFromVideoObject(fetchedVideo) |
231 | const { autoBlacklisted, videoCreated } = await retryTransactionWrapper(createVideo, fetchedVideo, channelActor, syncParam.thumbnail) | 257 | const videoChannel = actor.VideoChannel |
258 | const { autoBlacklisted, videoCreated } = await retryTransactionWrapper(createVideo, fetchedVideo, videoChannel, syncParam.thumbnail) | ||
232 | 259 | ||
233 | await syncVideoExternalAttributes(videoCreated, fetchedVideo, syncParam) | 260 | await syncVideoExternalAttributes(videoCreated, fetchedVideo, syncParam) |
234 | 261 | ||
@@ -236,22 +263,22 @@ async function getOrCreateVideoAndAccountAndChannel (options: { | |||
236 | } | 263 | } |
237 | 264 | ||
238 | async function updateVideoFromAP (options: { | 265 | async function updateVideoFromAP (options: { |
239 | video: VideoModel, | 266 | video: MVideoAccountLightBlacklistAllFiles, |
240 | videoObject: VideoTorrentObject, | 267 | videoObject: VideoTorrentObject, |
241 | account: AccountModelIdActor, | 268 | account: MAccountIdActor, |
242 | channel: VideoChannelModelIdActor, | 269 | channel: MChannelDefault, |
243 | overrideTo?: string[] | 270 | overrideTo?: string[] |
244 | }) { | 271 | }) { |
245 | const { video, videoObject, account, channel, overrideTo } = options | 272 | const { video, videoObject, account, channel, overrideTo } = options |
246 | 273 | ||
247 | logger.debug('Updating remote video "%s".', options.videoObject.uuid) | 274 | logger.debug('Updating remote video "%s".', options.videoObject.uuid, { account, channel }) |
248 | 275 | ||
249 | let videoFieldsSave: any | 276 | let videoFieldsSave: any |
250 | const wasPrivateVideo = video.privacy === VideoPrivacy.PRIVATE | 277 | const wasPrivateVideo = video.privacy === VideoPrivacy.PRIVATE |
251 | const wasUnlistedVideo = video.privacy === VideoPrivacy.UNLISTED | 278 | const wasUnlistedVideo = video.privacy === VideoPrivacy.UNLISTED |
252 | 279 | ||
253 | try { | 280 | try { |
254 | let thumbnailModel: ThumbnailModel | 281 | let thumbnailModel: MThumbnail |
255 | 282 | ||
256 | try { | 283 | try { |
257 | thumbnailModel = await createVideoMiniatureFromUrl(videoObject.icon.url, video, ThumbnailType.MINIATURE) | 284 | thumbnailModel = await createVideoMiniatureFromUrl(videoObject.icon.url, video, ThumbnailType.MINIATURE) |
@@ -259,7 +286,7 @@ async function updateVideoFromAP (options: { | |||
259 | logger.warn('Cannot generate thumbnail of %s.', videoObject.id, { err }) | 286 | logger.warn('Cannot generate thumbnail of %s.', videoObject.id, { err }) |
260 | } | 287 | } |
261 | 288 | ||
262 | await sequelizeTypescript.transaction(async t => { | 289 | const videoUpdated = await sequelizeTypescript.transaction(async t => { |
263 | const sequelizeOptions = { transaction: t } | 290 | const sequelizeOptions = { transaction: t } |
264 | 291 | ||
265 | videoFieldsSave = video.toJSON() | 292 | videoFieldsSave = video.toJSON() |
@@ -293,21 +320,21 @@ async function updateVideoFromAP (options: { | |||
293 | video.channelId = videoData.channelId | 320 | video.channelId = videoData.channelId |
294 | video.views = videoData.views | 321 | video.views = videoData.views |
295 | 322 | ||
296 | await video.save(sequelizeOptions) | 323 | const videoUpdated = await video.save(sequelizeOptions) as MVideoFullLight |
297 | 324 | ||
298 | if (thumbnailModel) await video.addAndSaveThumbnail(thumbnailModel, t) | 325 | if (thumbnailModel) await videoUpdated.addAndSaveThumbnail(thumbnailModel, t) |
299 | 326 | ||
300 | // FIXME: use icon URL instead | 327 | // FIXME: use icon URL instead |
301 | const previewUrl = buildRemoteBaseUrl(video, join(STATIC_PATHS.PREVIEWS, video.getPreview().filename)) | 328 | const previewUrl = buildRemoteBaseUrl(videoUpdated, join(STATIC_PATHS.PREVIEWS, videoUpdated.getPreview().filename)) |
302 | const previewModel = createPlaceholderThumbnail(previewUrl, video, ThumbnailType.PREVIEW, PREVIEWS_SIZE) | 329 | const previewModel = createPlaceholderThumbnail(previewUrl, video, ThumbnailType.PREVIEW, PREVIEWS_SIZE) |
303 | await video.addAndSaveThumbnail(previewModel, t) | 330 | await videoUpdated.addAndSaveThumbnail(previewModel, t) |
304 | 331 | ||
305 | { | 332 | { |
306 | const videoFileAttributes = videoFileActivityUrlToDBAttributes(video, videoObject) | 333 | const videoFileAttributes = videoFileActivityUrlToDBAttributes(videoUpdated, videoObject) |
307 | const newVideoFiles = videoFileAttributes.map(a => new VideoFileModel(a)) | 334 | const newVideoFiles = videoFileAttributes.map(a => new VideoFileModel(a)) |
308 | 335 | ||
309 | // Remove video files that do not exist anymore | 336 | // Remove video files that do not exist anymore |
310 | const destroyTasks = video.VideoFiles | 337 | const destroyTasks = videoUpdated.VideoFiles |
311 | .filter(f => !newVideoFiles.find(newFile => newFile.hasSameUniqueKeysThan(f))) | 338 | .filter(f => !newVideoFiles.find(newFile => newFile.hasSameUniqueKeysThan(f))) |
312 | .map(f => f.destroy(sequelizeOptions)) | 339 | .map(f => f.destroy(sequelizeOptions)) |
313 | await Promise.all(destroyTasks) | 340 | await Promise.all(destroyTasks) |
@@ -318,15 +345,15 @@ async function updateVideoFromAP (options: { | |||
318 | .then(([ file ]) => file) | 345 | .then(([ file ]) => file) |
319 | }) | 346 | }) |
320 | 347 | ||
321 | video.VideoFiles = await Promise.all(upsertTasks) | 348 | videoUpdated.VideoFiles = await Promise.all(upsertTasks) |
322 | } | 349 | } |
323 | 350 | ||
324 | { | 351 | { |
325 | const streamingPlaylistAttributes = streamingPlaylistActivityUrlToDBAttributes(video, videoObject, video.VideoFiles) | 352 | const streamingPlaylistAttributes = streamingPlaylistActivityUrlToDBAttributes(videoUpdated, videoObject, videoUpdated.VideoFiles) |
326 | const newStreamingPlaylists = streamingPlaylistAttributes.map(a => new VideoStreamingPlaylistModel(a)) | 353 | const newStreamingPlaylists = streamingPlaylistAttributes.map(a => new VideoStreamingPlaylistModel(a)) |
327 | 354 | ||
328 | // Remove video files that do not exist anymore | 355 | // Remove video files that do not exist anymore |
329 | const destroyTasks = video.VideoStreamingPlaylists | 356 | const destroyTasks = videoUpdated.VideoStreamingPlaylists |
330 | .filter(f => !newStreamingPlaylists.find(newPlaylist => newPlaylist.hasSameUniqueKeysThan(f))) | 357 | .filter(f => !newStreamingPlaylists.find(newPlaylist => newPlaylist.hasSameUniqueKeysThan(f))) |
331 | .map(f => f.destroy(sequelizeOptions)) | 358 | .map(f => f.destroy(sequelizeOptions)) |
332 | await Promise.all(destroyTasks) | 359 | await Promise.all(destroyTasks) |
@@ -337,38 +364,42 @@ async function updateVideoFromAP (options: { | |||
337 | .then(([ streamingPlaylist ]) => streamingPlaylist) | 364 | .then(([ streamingPlaylist ]) => streamingPlaylist) |
338 | }) | 365 | }) |
339 | 366 | ||
340 | video.VideoStreamingPlaylists = await Promise.all(upsertTasks) | 367 | videoUpdated.VideoStreamingPlaylists = await Promise.all(upsertTasks) |
341 | } | 368 | } |
342 | 369 | ||
343 | { | 370 | { |
344 | // Update Tags | 371 | // Update Tags |
345 | const tags = videoObject.tag.map(tag => tag.name) | 372 | const tags = videoObject.tag.map(tag => tag.name) |
346 | const tagInstances = await TagModel.findOrCreateTags(tags, t) | 373 | const tagInstances = await TagModel.findOrCreateTags(tags, t) |
347 | await video.$set('Tags', tagInstances, sequelizeOptions) | 374 | await videoUpdated.$set('Tags', tagInstances, sequelizeOptions) |
348 | } | 375 | } |
349 | 376 | ||
350 | { | 377 | { |
351 | // Update captions | 378 | // Update captions |
352 | await VideoCaptionModel.deleteAllCaptionsOfRemoteVideo(video.id, t) | 379 | await VideoCaptionModel.deleteAllCaptionsOfRemoteVideo(videoUpdated.id, t) |
353 | 380 | ||
354 | const videoCaptionsPromises = videoObject.subtitleLanguage.map(c => { | 381 | const videoCaptionsPromises = videoObject.subtitleLanguage.map(c => { |
355 | return VideoCaptionModel.insertOrReplaceLanguage(video.id, c.identifier, t) | 382 | return VideoCaptionModel.insertOrReplaceLanguage(videoUpdated.id, c.identifier, t) |
356 | }) | 383 | }) |
357 | video.VideoCaptions = await Promise.all(videoCaptionsPromises) | 384 | await Promise.all(videoCaptionsPromises) |
358 | } | 385 | } |
386 | |||
387 | return videoUpdated | ||
359 | }) | 388 | }) |
360 | 389 | ||
361 | await autoBlacklistVideoIfNeeded({ | 390 | await autoBlacklistVideoIfNeeded({ |
362 | video, | 391 | video: videoUpdated, |
363 | user: undefined, | 392 | user: undefined, |
364 | isRemote: true, | 393 | isRemote: true, |
365 | isNew: false, | 394 | isNew: false, |
366 | transaction: undefined | 395 | transaction: undefined |
367 | }) | 396 | }) |
368 | 397 | ||
369 | if (wasPrivateVideo || wasUnlistedVideo) Notifier.Instance.notifyOnNewVideoIfNeeded(video) // Notify our users? | 398 | if (wasPrivateVideo || wasUnlistedVideo) Notifier.Instance.notifyOnNewVideoIfNeeded(videoUpdated) // Notify our users? |
370 | 399 | ||
371 | logger.info('Remote video with uuid %s updated', videoObject.uuid) | 400 | logger.info('Remote video with uuid %s updated', videoObject.uuid) |
401 | |||
402 | return videoUpdated | ||
372 | } catch (err) { | 403 | } catch (err) { |
373 | if (video !== undefined && videoFieldsSave !== undefined) { | 404 | if (video !== undefined && videoFieldsSave !== undefined) { |
374 | resetSequelizeInstance(video, videoFieldsSave) | 405 | resetSequelizeInstance(video, videoFieldsSave) |
@@ -381,15 +412,15 @@ async function updateVideoFromAP (options: { | |||
381 | } | 412 | } |
382 | 413 | ||
383 | async function refreshVideoIfNeeded (options: { | 414 | async function refreshVideoIfNeeded (options: { |
384 | video: VideoModel, | 415 | video: MVideoThumbnail, |
385 | fetchedType: VideoFetchByUrlType, | 416 | fetchedType: VideoFetchByUrlType, |
386 | syncParam: SyncParam | 417 | syncParam: SyncParam |
387 | }): Promise<VideoModel> { | 418 | }): Promise<MVideoThumbnail> { |
388 | if (!options.video.isOutdated()) return options.video | 419 | if (!options.video.isOutdated()) return options.video |
389 | 420 | ||
390 | // We need more attributes if the argument video was fetched with not enough joints | 421 | // We need more attributes if the argument video was fetched with not enough joints |
391 | const video = options.fetchedType === 'all' | 422 | const video = options.fetchedType === 'all' |
392 | ? options.video | 423 | ? options.video as MVideoAccountLightBlacklistAllFiles |
393 | : await VideoModel.loadByUrlAndPopulateAccount(options.video.url) | 424 | : await VideoModel.loadByUrlAndPopulateAccount(options.video.url) |
394 | 425 | ||
395 | try { | 426 | try { |
@@ -410,12 +441,11 @@ async function refreshVideoIfNeeded (options: { | |||
410 | } | 441 | } |
411 | 442 | ||
412 | const channelActor = await getOrCreateVideoChannelFromVideoObject(videoObject) | 443 | const channelActor = await getOrCreateVideoChannelFromVideoObject(videoObject) |
413 | const account = await AccountModel.load(channelActor.VideoChannel.accountId) | ||
414 | 444 | ||
415 | const updateOptions = { | 445 | const updateOptions = { |
416 | video, | 446 | video, |
417 | videoObject, | 447 | videoObject, |
418 | account, | 448 | account: channelActor.VideoChannel.Account, |
419 | channel: channelActor.VideoChannel | 449 | channel: channelActor.VideoChannel |
420 | } | 450 | } |
421 | await retryTransactionWrapper(updateVideoFromAP, updateOptions) | 451 | await retryTransactionWrapper(updateVideoFromAP, updateOptions) |
@@ -467,15 +497,15 @@ function isAPPlaylistSegmentHashesUrlObject (tag: any): tag is ActivityPlaylistS | |||
467 | return tag.name === 'sha256' && tag.type === 'Link' && urlMediaType === 'application/json' | 497 | return tag.name === 'sha256' && tag.type === 'Link' && urlMediaType === 'application/json' |
468 | } | 498 | } |
469 | 499 | ||
470 | async function createVideo (videoObject: VideoTorrentObject, channelActor: ActorModel, waitThumbnail = false) { | 500 | async function createVideo (videoObject: VideoTorrentObject, channel: MChannelAccountLight, waitThumbnail = false) { |
471 | logger.debug('Adding remote video %s.', videoObject.id) | 501 | logger.debug('Adding remote video %s.', videoObject.id) |
472 | 502 | ||
473 | const videoData = await videoActivityObjectToDBAttributes(channelActor.VideoChannel, videoObject, videoObject.to) | 503 | const videoData = await videoActivityObjectToDBAttributes(channel, videoObject, videoObject.to) |
474 | const video = VideoModel.build(videoData) | 504 | const video = VideoModel.build(videoData) as MVideoThumbnail |
475 | 505 | ||
476 | const promiseThumbnail = createVideoMiniatureFromUrl(videoObject.icon.url, video, ThumbnailType.MINIATURE) | 506 | const promiseThumbnail = createVideoMiniatureFromUrl(videoObject.icon.url, video, ThumbnailType.MINIATURE) |
477 | 507 | ||
478 | let thumbnailModel: ThumbnailModel | 508 | let thumbnailModel: MThumbnail |
479 | if (waitThumbnail === true) { | 509 | if (waitThumbnail === true) { |
480 | thumbnailModel = await promiseThumbnail | 510 | thumbnailModel = await promiseThumbnail |
481 | } | 511 | } |
@@ -483,8 +513,8 @@ async function createVideo (videoObject: VideoTorrentObject, channelActor: Actor | |||
483 | const { autoBlacklisted, videoCreated } = await sequelizeTypescript.transaction(async t => { | 513 | const { autoBlacklisted, videoCreated } = await sequelizeTypescript.transaction(async t => { |
484 | const sequelizeOptions = { transaction: t } | 514 | const sequelizeOptions = { transaction: t } |
485 | 515 | ||
486 | const videoCreated = await video.save(sequelizeOptions) | 516 | const videoCreated = await video.save(sequelizeOptions) as MVideoFullLight |
487 | videoCreated.VideoChannel = channelActor.VideoChannel | 517 | videoCreated.VideoChannel = channel |
488 | 518 | ||
489 | if (thumbnailModel) await videoCreated.addAndSaveThumbnail(thumbnailModel, t) | 519 | if (thumbnailModel) await videoCreated.addAndSaveThumbnail(thumbnailModel, t) |
490 | 520 | ||
@@ -517,15 +547,14 @@ async function createVideo (videoObject: VideoTorrentObject, channelActor: Actor | |||
517 | const videoCaptionsPromises = videoObject.subtitleLanguage.map(c => { | 547 | const videoCaptionsPromises = videoObject.subtitleLanguage.map(c => { |
518 | return VideoCaptionModel.insertOrReplaceLanguage(videoCreated.id, c.identifier, t) | 548 | return VideoCaptionModel.insertOrReplaceLanguage(videoCreated.id, c.identifier, t) |
519 | }) | 549 | }) |
520 | const captions = await Promise.all(videoCaptionsPromises) | 550 | await Promise.all(videoCaptionsPromises) |
521 | 551 | ||
522 | video.VideoFiles = videoFiles | 552 | videoCreated.VideoFiles = videoFiles |
523 | video.VideoStreamingPlaylists = streamingPlaylists | 553 | videoCreated.VideoStreamingPlaylists = streamingPlaylists |
524 | video.Tags = tagInstances | 554 | videoCreated.Tags = tagInstances |
525 | video.VideoCaptions = captions | ||
526 | 555 | ||
527 | const autoBlacklisted = await autoBlacklistVideoIfNeeded({ | 556 | const autoBlacklisted = await autoBlacklistVideoIfNeeded({ |
528 | video, | 557 | video: videoCreated, |
529 | user: undefined, | 558 | user: undefined, |
530 | isRemote: true, | 559 | isRemote: true, |
531 | isNew: true, | 560 | isNew: true, |
@@ -548,11 +577,7 @@ async function createVideo (videoObject: VideoTorrentObject, channelActor: Actor | |||
548 | return { autoBlacklisted, videoCreated } | 577 | return { autoBlacklisted, videoCreated } |
549 | } | 578 | } |
550 | 579 | ||
551 | async function videoActivityObjectToDBAttributes ( | 580 | async function videoActivityObjectToDBAttributes (videoChannel: MChannelId, videoObject: VideoTorrentObject, to: string[] = []) { |
552 | videoChannel: VideoChannelModelId, | ||
553 | videoObject: VideoTorrentObject, | ||
554 | to: string[] = [] | ||
555 | ) { | ||
556 | const privacy = to.indexOf(ACTIVITY_PUB.PUBLIC) !== -1 ? VideoPrivacy.PUBLIC : VideoPrivacy.UNLISTED | 581 | const privacy = to.indexOf(ACTIVITY_PUB.PUBLIC) !== -1 ? VideoPrivacy.PUBLIC : VideoPrivacy.UNLISTED |
557 | const duration = videoObject.duration.replace(/[^\d]+/, '') | 582 | const duration = videoObject.duration.replace(/[^\d]+/, '') |
558 | 583 | ||
@@ -603,7 +628,7 @@ async function videoActivityObjectToDBAttributes ( | |||
603 | } | 628 | } |
604 | } | 629 | } |
605 | 630 | ||
606 | function videoFileActivityUrlToDBAttributes (video: VideoModel, videoObject: VideoTorrentObject) { | 631 | function videoFileActivityUrlToDBAttributes (video: MVideo, videoObject: VideoTorrentObject) { |
607 | const fileUrls = videoObject.url.filter(u => isAPVideoUrlObject(u)) as ActivityVideoUrlObject[] | 632 | const fileUrls = videoObject.url.filter(u => isAPVideoUrlObject(u)) as ActivityVideoUrlObject[] |
608 | 633 | ||
609 | if (fileUrls.length === 0) { | 634 | if (fileUrls.length === 0) { |
@@ -641,7 +666,7 @@ function videoFileActivityUrlToDBAttributes (video: VideoModel, videoObject: Vid | |||
641 | return attributes | 666 | return attributes |
642 | } | 667 | } |
643 | 668 | ||
644 | function streamingPlaylistActivityUrlToDBAttributes (video: VideoModel, videoObject: VideoTorrentObject, videoFiles: VideoFileModel[]) { | 669 | function streamingPlaylistActivityUrlToDBAttributes (video: MVideoId, videoObject: VideoTorrentObject, videoFiles: MVideoFile[]) { |
645 | const playlistUrls = videoObject.url.filter(u => isAPStreamingPlaylistUrlObject(u)) as ActivityPlaylistUrlObject[] | 670 | const playlistUrls = videoObject.url.filter(u => isAPStreamingPlaylistUrlObject(u)) as ActivityPlaylistUrlObject[] |
646 | if (playlistUrls.length === 0) return [] | 671 | if (playlistUrls.length === 0) return [] |
647 | 672 | ||
diff --git a/server/lib/avatar.ts b/server/lib/avatar.ts index 1b38e6cb5..ad4cdd3ab 100644 --- a/server/lib/avatar.ts +++ b/server/lib/avatar.ts | |||
@@ -3,8 +3,6 @@ import { sendUpdateActor } from './activitypub/send' | |||
3 | import { AVATARS_SIZE, LRU_CACHE, QUEUE_CONCURRENCY } from '../initializers/constants' | 3 | import { AVATARS_SIZE, LRU_CACHE, QUEUE_CONCURRENCY } from '../initializers/constants' |
4 | import { updateActorAvatarInstance } from './activitypub' | 4 | import { updateActorAvatarInstance } from './activitypub' |
5 | import { processImage } from '../helpers/image-utils' | 5 | import { processImage } from '../helpers/image-utils' |
6 | import { AccountModel } from '../models/account/account' | ||
7 | import { VideoChannelModel } from '../models/video/video-channel' | ||
8 | import { extname, join } from 'path' | 6 | import { extname, join } from 'path' |
9 | import { retryTransactionWrapper } from '../helpers/database-utils' | 7 | import { retryTransactionWrapper } from '../helpers/database-utils' |
10 | import * as uuidv4 from 'uuid/v4' | 8 | import * as uuidv4 from 'uuid/v4' |
@@ -13,8 +11,12 @@ import { sequelizeTypescript } from '../initializers/database' | |||
13 | import * as LRUCache from 'lru-cache' | 11 | import * as LRUCache from 'lru-cache' |
14 | import { queue } from 'async' | 12 | import { queue } from 'async' |
15 | import { downloadImage } from '../helpers/requests' | 13 | import { downloadImage } from '../helpers/requests' |
14 | import { MAccountDefault, MChannelDefault } from '../typings/models' | ||
16 | 15 | ||
17 | async function updateActorAvatarFile (avatarPhysicalFile: Express.Multer.File, accountOrChannel: AccountModel | VideoChannelModel) { | 16 | async function updateActorAvatarFile ( |
17 | avatarPhysicalFile: Express.Multer.File, | ||
18 | accountOrChannel: MAccountDefault | MChannelDefault | ||
19 | ) { | ||
18 | const extension = extname(avatarPhysicalFile.filename) | 20 | const extension = extname(avatarPhysicalFile.filename) |
19 | const avatarName = uuidv4() + extension | 21 | const avatarName = uuidv4() + extension |
20 | const destination = join(CONFIG.STORAGE.AVATARS_DIR, avatarName) | 22 | const destination = join(CONFIG.STORAGE.AVATARS_DIR, avatarName) |
diff --git a/server/lib/blocklist.ts b/server/lib/blocklist.ts index 1633e500c..28c69b46e 100644 --- a/server/lib/blocklist.ts +++ b/server/lib/blocklist.ts | |||
@@ -1,6 +1,7 @@ | |||
1 | import { sequelizeTypescript } from '../initializers' | 1 | import { sequelizeTypescript } from '../initializers' |
2 | import { AccountBlocklistModel } from '../models/account/account-blocklist' | 2 | import { AccountBlocklistModel } from '../models/account/account-blocklist' |
3 | import { ServerBlocklistModel } from '../models/server/server-blocklist' | 3 | import { ServerBlocklistModel } from '../models/server/server-blocklist' |
4 | import { MAccountBlocklist, MServerBlocklist } from '@server/typings/models' | ||
4 | 5 | ||
5 | function addAccountInBlocklist (byAccountId: number, targetAccountId: number) { | 6 | function addAccountInBlocklist (byAccountId: number, targetAccountId: number) { |
6 | return sequelizeTypescript.transaction(async t => { | 7 | return sequelizeTypescript.transaction(async t => { |
@@ -20,13 +21,13 @@ function addServerInBlocklist (byAccountId: number, targetServerId: number) { | |||
20 | }) | 21 | }) |
21 | } | 22 | } |
22 | 23 | ||
23 | function removeAccountFromBlocklist (accountBlock: AccountBlocklistModel) { | 24 | function removeAccountFromBlocklist (accountBlock: MAccountBlocklist) { |
24 | return sequelizeTypescript.transaction(async t => { | 25 | return sequelizeTypescript.transaction(async t => { |
25 | return accountBlock.destroy({ transaction: t }) | 26 | return accountBlock.destroy({ transaction: t }) |
26 | }) | 27 | }) |
27 | } | 28 | } |
28 | 29 | ||
29 | function removeServerFromBlocklist (serverBlock: ServerBlocklistModel) { | 30 | function removeServerFromBlocklist (serverBlock: MServerBlocklist) { |
30 | return sequelizeTypescript.transaction(async t => { | 31 | return sequelizeTypescript.transaction(async t => { |
31 | return serverBlock.destroy({ transaction: t }) | 32 | return serverBlock.destroy({ transaction: t }) |
32 | }) | 33 | }) |
diff --git a/server/lib/client-html.ts b/server/lib/client-html.ts index 8841dd2ac..a1f4ae858 100644 --- a/server/lib/client-html.ts +++ b/server/lib/client-html.ts | |||
@@ -13,6 +13,7 @@ import { VideoChannelModel } from '../models/video/video-channel' | |||
13 | import * as Bluebird from 'bluebird' | 13 | import * as Bluebird from 'bluebird' |
14 | import { CONFIG } from '../initializers/config' | 14 | import { CONFIG } from '../initializers/config' |
15 | import { logger } from '../helpers/logger' | 15 | import { logger } from '../helpers/logger' |
16 | import { MAccountActor, MChannelActor, MVideo } from '../typings/models' | ||
16 | 17 | ||
17 | export class ClientHtml { | 18 | export class ClientHtml { |
18 | 19 | ||
@@ -41,11 +42,11 @@ export class ClientHtml { | |||
41 | 42 | ||
42 | const [ html, video ] = await Promise.all([ | 43 | const [ html, video ] = await Promise.all([ |
43 | ClientHtml.getIndexHTML(req, res), | 44 | ClientHtml.getIndexHTML(req, res), |
44 | VideoModel.load(videoId) | 45 | VideoModel.loadWithBlacklist(videoId) |
45 | ]) | 46 | ]) |
46 | 47 | ||
47 | // Let Angular application handle errors | 48 | // Let Angular application handle errors |
48 | if (!video || video.privacy === VideoPrivacy.PRIVATE) { | 49 | if (!video || video.privacy === VideoPrivacy.PRIVATE || video.VideoBlacklist) { |
49 | return ClientHtml.getIndexHTML(req, res) | 50 | return ClientHtml.getIndexHTML(req, res) |
50 | } | 51 | } |
51 | 52 | ||
@@ -65,7 +66,7 @@ export class ClientHtml { | |||
65 | } | 66 | } |
66 | 67 | ||
67 | private static async getAccountOrChannelHTMLPage ( | 68 | private static async getAccountOrChannelHTMLPage ( |
68 | loader: () => Bluebird<AccountModel | VideoChannelModel>, | 69 | loader: () => Bluebird<MAccountActor | MChannelActor>, |
69 | req: express.Request, | 70 | req: express.Request, |
70 | res: express.Response | 71 | res: express.Response |
71 | ) { | 72 | ) { |
@@ -157,7 +158,7 @@ export class ClientHtml { | |||
157 | return htmlStringPage.replace('</head>', linkTag + '</head>') | 158 | return htmlStringPage.replace('</head>', linkTag + '</head>') |
158 | } | 159 | } |
159 | 160 | ||
160 | private static addVideoOpenGraphAndOEmbedTags (htmlStringPage: string, video: VideoModel) { | 161 | private static addVideoOpenGraphAndOEmbedTags (htmlStringPage: string, video: MVideo) { |
161 | const previewUrl = WEBSERVER.URL + video.getPreviewStaticPath() | 162 | const previewUrl = WEBSERVER.URL + video.getPreviewStaticPath() |
162 | const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() | 163 | const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() |
163 | 164 | ||
@@ -236,7 +237,7 @@ export class ClientHtml { | |||
236 | return this.addOpenGraphAndOEmbedTags(htmlStringPage, tagsString) | 237 | return this.addOpenGraphAndOEmbedTags(htmlStringPage, tagsString) |
237 | } | 238 | } |
238 | 239 | ||
239 | private static addAccountOrChannelMetaTags (htmlStringPage: string, entity: AccountModel | VideoChannelModel) { | 240 | private static addAccountOrChannelMetaTags (htmlStringPage: string, entity: MAccountActor | MChannelActor) { |
240 | // SEO, use origin account or channel URL | 241 | // SEO, use origin account or channel URL |
241 | const metaTags = `<link rel="canonical" href="${entity.Actor.url}" />` | 242 | const metaTags = `<link rel="canonical" href="${entity.Actor.url}" />` |
242 | 243 | ||
diff --git a/server/lib/emailer.ts b/server/lib/emailer.ts index 10e7d0479..bd3d4f252 100644 --- a/server/lib/emailer.ts +++ b/server/lib/emailer.ts | |||
@@ -2,17 +2,20 @@ import { createTransport, Transporter } from 'nodemailer' | |||
2 | import { isTestInstance } from '../helpers/core-utils' | 2 | import { isTestInstance } from '../helpers/core-utils' |
3 | import { bunyanLogger, logger } from '../helpers/logger' | 3 | import { bunyanLogger, logger } from '../helpers/logger' |
4 | import { CONFIG } from '../initializers/config' | 4 | import { CONFIG } from '../initializers/config' |
5 | import { UserModel } from '../models/account/user' | ||
6 | import { VideoModel } from '../models/video/video' | ||
7 | import { JobQueue } from './job-queue' | 5 | import { JobQueue } from './job-queue' |
8 | import { EmailPayload } from './job-queue/handlers/email' | 6 | import { EmailPayload } from './job-queue/handlers/email' |
9 | import { readFileSync } from 'fs-extra' | 7 | import { readFileSync } from 'fs-extra' |
10 | import { VideoCommentModel } from '../models/video/video-comment' | ||
11 | import { VideoAbuseModel } from '../models/video/video-abuse' | ||
12 | import { VideoBlacklistModel } from '../models/video/video-blacklist' | ||
13 | import { VideoImportModel } from '../models/video/video-import' | ||
14 | import { ActorFollowModel } from '../models/activitypub/actor-follow' | ||
15 | import { WEBSERVER } from '../initializers/constants' | 8 | import { WEBSERVER } from '../initializers/constants' |
9 | import { | ||
10 | MCommentOwnerVideo, | ||
11 | MVideo, | ||
12 | MVideoAbuseVideo, | ||
13 | MVideoAccountLight, | ||
14 | MVideoBlacklistLightVideo, | ||
15 | MVideoBlacklistVideo | ||
16 | } from '../typings/models/video' | ||
17 | import { MActorFollowActors, MActorFollowFull, MUser } from '../typings/models' | ||
18 | import { MVideoImport, MVideoImportVideo } from '@server/typings/models/video/video-import' | ||
16 | 19 | ||
17 | type SendEmailOptions = { | 20 | type SendEmailOptions = { |
18 | to: string[] | 21 | to: string[] |
@@ -90,7 +93,7 @@ class Emailer { | |||
90 | } | 93 | } |
91 | } | 94 | } |
92 | 95 | ||
93 | addNewVideoFromSubscriberNotification (to: string[], video: VideoModel) { | 96 | addNewVideoFromSubscriberNotification (to: string[], video: MVideoAccountLight) { |
94 | const channelName = video.VideoChannel.getDisplayName() | 97 | const channelName = video.VideoChannel.getDisplayName() |
95 | const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() | 98 | const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() |
96 | 99 | ||
@@ -111,7 +114,7 @@ class Emailer { | |||
111 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 114 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
112 | } | 115 | } |
113 | 116 | ||
114 | addNewFollowNotification (to: string[], actorFollow: ActorFollowModel, followType: 'account' | 'channel') { | 117 | addNewFollowNotification (to: string[], actorFollow: MActorFollowFull, followType: 'account' | 'channel') { |
115 | const followerName = actorFollow.ActorFollower.Account.getDisplayName() | 118 | const followerName = actorFollow.ActorFollower.Account.getDisplayName() |
116 | const followingName = (actorFollow.ActorFollowing.VideoChannel || actorFollow.ActorFollowing.Account).getDisplayName() | 119 | const followingName = (actorFollow.ActorFollowing.VideoChannel || actorFollow.ActorFollowing.Account).getDisplayName() |
117 | 120 | ||
@@ -130,7 +133,7 @@ class Emailer { | |||
130 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 133 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
131 | } | 134 | } |
132 | 135 | ||
133 | addNewInstanceFollowerNotification (to: string[], actorFollow: ActorFollowModel) { | 136 | addNewInstanceFollowerNotification (to: string[], actorFollow: MActorFollowActors) { |
134 | const awaitingApproval = actorFollow.state === 'pending' ? ' awaiting manual approval.' : '' | 137 | const awaitingApproval = actorFollow.state === 'pending' ? ' awaiting manual approval.' : '' |
135 | 138 | ||
136 | const text = `Hi dear admin,\n\n` + | 139 | const text = `Hi dear admin,\n\n` + |
@@ -148,7 +151,23 @@ class Emailer { | |||
148 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 151 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
149 | } | 152 | } |
150 | 153 | ||
151 | myVideoPublishedNotification (to: string[], video: VideoModel) { | 154 | addAutoInstanceFollowingNotification (to: string[], actorFollow: MActorFollowActors) { |
155 | const text = `Hi dear admin,\n\n` + | ||
156 | `Your instance automatically followed a new instance: ${actorFollow.ActorFollowing.url}` + | ||
157 | `\n\n` + | ||
158 | `Cheers,\n` + | ||
159 | `${CONFIG.EMAIL.BODY.SIGNATURE}` | ||
160 | |||
161 | const emailPayload: EmailPayload = { | ||
162 | to, | ||
163 | subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'Auto instance following', | ||
164 | text | ||
165 | } | ||
166 | |||
167 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | ||
168 | } | ||
169 | |||
170 | myVideoPublishedNotification (to: string[], video: MVideo) { | ||
152 | const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() | 171 | const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() |
153 | 172 | ||
154 | const text = `Hi dear user,\n\n` + | 173 | const text = `Hi dear user,\n\n` + |
@@ -168,7 +187,7 @@ class Emailer { | |||
168 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 187 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
169 | } | 188 | } |
170 | 189 | ||
171 | myVideoImportSuccessNotification (to: string[], videoImport: VideoImportModel) { | 190 | myVideoImportSuccessNotification (to: string[], videoImport: MVideoImportVideo) { |
172 | const videoUrl = WEBSERVER.URL + videoImport.Video.getWatchStaticPath() | 191 | const videoUrl = WEBSERVER.URL + videoImport.Video.getWatchStaticPath() |
173 | 192 | ||
174 | const text = `Hi dear user,\n\n` + | 193 | const text = `Hi dear user,\n\n` + |
@@ -188,7 +207,7 @@ class Emailer { | |||
188 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 207 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
189 | } | 208 | } |
190 | 209 | ||
191 | myVideoImportErrorNotification (to: string[], videoImport: VideoImportModel) { | 210 | myVideoImportErrorNotification (to: string[], videoImport: MVideoImport) { |
192 | const importUrl = WEBSERVER.URL + '/my-account/video-imports' | 211 | const importUrl = WEBSERVER.URL + '/my-account/video-imports' |
193 | 212 | ||
194 | const text = `Hi dear user,\n\n` + | 213 | const text = `Hi dear user,\n\n` + |
@@ -208,7 +227,7 @@ class Emailer { | |||
208 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 227 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
209 | } | 228 | } |
210 | 229 | ||
211 | addNewCommentOnMyVideoNotification (to: string[], comment: VideoCommentModel) { | 230 | addNewCommentOnMyVideoNotification (to: string[], comment: MCommentOwnerVideo) { |
212 | const accountName = comment.Account.getDisplayName() | 231 | const accountName = comment.Account.getDisplayName() |
213 | const video = comment.Video | 232 | const video = comment.Video |
214 | const commentUrl = WEBSERVER.URL + comment.getCommentStaticPath() | 233 | const commentUrl = WEBSERVER.URL + comment.getCommentStaticPath() |
@@ -230,7 +249,7 @@ class Emailer { | |||
230 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 249 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
231 | } | 250 | } |
232 | 251 | ||
233 | addNewCommentMentionNotification (to: string[], comment: VideoCommentModel) { | 252 | addNewCommentMentionNotification (to: string[], comment: MCommentOwnerVideo) { |
234 | const accountName = comment.Account.getDisplayName() | 253 | const accountName = comment.Account.getDisplayName() |
235 | const video = comment.Video | 254 | const video = comment.Video |
236 | const commentUrl = WEBSERVER.URL + comment.getCommentStaticPath() | 255 | const commentUrl = WEBSERVER.URL + comment.getCommentStaticPath() |
@@ -252,7 +271,7 @@ class Emailer { | |||
252 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 271 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
253 | } | 272 | } |
254 | 273 | ||
255 | addVideoAbuseModeratorsNotification (to: string[], videoAbuse: VideoAbuseModel) { | 274 | addVideoAbuseModeratorsNotification (to: string[], videoAbuse: MVideoAbuseVideo) { |
256 | const videoUrl = WEBSERVER.URL + videoAbuse.Video.getWatchStaticPath() | 275 | const videoUrl = WEBSERVER.URL + videoAbuse.Video.getWatchStaticPath() |
257 | 276 | ||
258 | const text = `Hi,\n\n` + | 277 | const text = `Hi,\n\n` + |
@@ -269,9 +288,9 @@ class Emailer { | |||
269 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 288 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
270 | } | 289 | } |
271 | 290 | ||
272 | addVideoAutoBlacklistModeratorsNotification (to: string[], video: VideoModel) { | 291 | addVideoAutoBlacklistModeratorsNotification (to: string[], videoBlacklist: MVideoBlacklistLightVideo) { |
273 | const VIDEO_AUTO_BLACKLIST_URL = WEBSERVER.URL + '/admin/moderation/video-auto-blacklist/list' | 292 | const VIDEO_AUTO_BLACKLIST_URL = WEBSERVER.URL + '/admin/moderation/video-auto-blacklist/list' |
274 | const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() | 293 | const videoUrl = WEBSERVER.URL + videoBlacklist.Video.getWatchStaticPath() |
275 | 294 | ||
276 | const text = `Hi,\n\n` + | 295 | const text = `Hi,\n\n` + |
277 | `A recently added video was auto-blacklisted and requires moderator review before publishing.` + | 296 | `A recently added video was auto-blacklisted and requires moderator review before publishing.` + |
@@ -292,7 +311,7 @@ class Emailer { | |||
292 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 311 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
293 | } | 312 | } |
294 | 313 | ||
295 | addNewUserRegistrationNotification (to: string[], user: UserModel) { | 314 | addNewUserRegistrationNotification (to: string[], user: MUser) { |
296 | const text = `Hi,\n\n` + | 315 | const text = `Hi,\n\n` + |
297 | `User ${user.username} just registered on ${WEBSERVER.HOST} PeerTube instance.\n\n` + | 316 | `User ${user.username} just registered on ${WEBSERVER.HOST} PeerTube instance.\n\n` + |
298 | `Cheers,\n` + | 317 | `Cheers,\n` + |
@@ -307,7 +326,7 @@ class Emailer { | |||
307 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 326 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
308 | } | 327 | } |
309 | 328 | ||
310 | addVideoBlacklistNotification (to: string[], videoBlacklist: VideoBlacklistModel) { | 329 | addVideoBlacklistNotification (to: string[], videoBlacklist: MVideoBlacklistVideo) { |
311 | const videoName = videoBlacklist.Video.name | 330 | const videoName = videoBlacklist.Video.name |
312 | const videoUrl = WEBSERVER.URL + videoBlacklist.Video.getWatchStaticPath() | 331 | const videoUrl = WEBSERVER.URL + videoBlacklist.Video.getWatchStaticPath() |
313 | 332 | ||
@@ -329,7 +348,7 @@ class Emailer { | |||
329 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 348 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
330 | } | 349 | } |
331 | 350 | ||
332 | addVideoUnblacklistNotification (to: string[], video: VideoModel) { | 351 | addVideoUnblacklistNotification (to: string[], video: MVideo) { |
333 | const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() | 352 | const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() |
334 | 353 | ||
335 | const text = 'Hi,\n\n' + | 354 | const text = 'Hi,\n\n' + |
@@ -381,7 +400,7 @@ class Emailer { | |||
381 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 400 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
382 | } | 401 | } |
383 | 402 | ||
384 | addUserBlockJob (user: UserModel, blocked: boolean, reason?: string) { | 403 | addUserBlockJob (user: MUser, blocked: boolean, reason?: string) { |
385 | const reasonString = reason ? ` for the following reason: ${reason}` : '' | 404 | const reasonString = reason ? ` for the following reason: ${reason}` : '' |
386 | const blockedWord = blocked ? 'blocked' : 'unblocked' | 405 | const blockedWord = blocked ? 'blocked' : 'unblocked' |
387 | const blockedString = `Your account ${user.username} on ${WEBSERVER.HOST} has been ${blockedWord}${reasonString}.` | 406 | const blockedString = `Your account ${user.username} on ${WEBSERVER.HOST} has been ${blockedWord}${reasonString}.` |
diff --git a/server/lib/hls.ts b/server/lib/hls.ts index 98da4dcd8..05136c21c 100644 --- a/server/lib/hls.ts +++ b/server/lib/hls.ts | |||
@@ -1,4 +1,3 @@ | |||
1 | import { VideoModel } from '../models/video/video' | ||
2 | import { basename, dirname, join } from 'path' | 1 | import { basename, dirname, join } from 'path' |
3 | import { HLS_STREAMING_PLAYLIST_DIRECTORY, P2P_MEDIA_LOADER_PEER_VERSION } from '../initializers/constants' | 2 | import { HLS_STREAMING_PLAYLIST_DIRECTORY, P2P_MEDIA_LOADER_PEER_VERSION } from '../initializers/constants' |
4 | import { close, ensureDir, move, open, outputJSON, pathExists, read, readFile, remove, writeFile } from 'fs-extra' | 3 | import { close, ensureDir, move, open, outputJSON, pathExists, read, readFile, remove, writeFile } from 'fs-extra' |
@@ -12,6 +11,7 @@ import { flatten, uniq } from 'lodash' | |||
12 | import { VideoFileModel } from '../models/video/video-file' | 11 | import { VideoFileModel } from '../models/video/video-file' |
13 | import { CONFIG } from '../initializers/config' | 12 | import { CONFIG } from '../initializers/config' |
14 | import { sequelizeTypescript } from '../initializers/database' | 13 | import { sequelizeTypescript } from '../initializers/database' |
14 | import { MVideoWithFile } from '@server/typings/models' | ||
15 | 15 | ||
16 | async function updateStreamingPlaylistsInfohashesIfNeeded () { | 16 | async function updateStreamingPlaylistsInfohashesIfNeeded () { |
17 | const playlistsToUpdate = await VideoStreamingPlaylistModel.listByIncorrectPeerVersion() | 17 | const playlistsToUpdate = await VideoStreamingPlaylistModel.listByIncorrectPeerVersion() |
@@ -28,7 +28,7 @@ async function updateStreamingPlaylistsInfohashesIfNeeded () { | |||
28 | } | 28 | } |
29 | } | 29 | } |
30 | 30 | ||
31 | async function updateMasterHLSPlaylist (video: VideoModel) { | 31 | async function updateMasterHLSPlaylist (video: MVideoWithFile) { |
32 | const directory = join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid) | 32 | const directory = join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid) |
33 | const masterPlaylists: string[] = [ '#EXTM3U', '#EXT-X-VERSION:3' ] | 33 | const masterPlaylists: string[] = [ '#EXTM3U', '#EXT-X-VERSION:3' ] |
34 | const masterPlaylistPath = join(directory, VideoStreamingPlaylistModel.getMasterHlsPlaylistFilename()) | 34 | const masterPlaylistPath = join(directory, VideoStreamingPlaylistModel.getMasterHlsPlaylistFilename()) |
@@ -55,7 +55,7 @@ async function updateMasterHLSPlaylist (video: VideoModel) { | |||
55 | await writeFile(masterPlaylistPath, masterPlaylists.join('\n') + '\n') | 55 | await writeFile(masterPlaylistPath, masterPlaylists.join('\n') + '\n') |
56 | } | 56 | } |
57 | 57 | ||
58 | async function updateSha256Segments (video: VideoModel) { | 58 | async function updateSha256Segments (video: MVideoWithFile) { |
59 | const json: { [filename: string]: { [range: string]: string } } = {} | 59 | const json: { [filename: string]: { [range: string]: string } } = {} |
60 | 60 | ||
61 | const playlistDirectory = join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid) | 61 | const playlistDirectory = join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid) |
diff --git a/server/lib/job-queue/handlers/activitypub-follow.ts b/server/lib/job-queue/handlers/activitypub-follow.ts index 4ae66cd01..af7c8a838 100644 --- a/server/lib/job-queue/handlers/activitypub-follow.ts +++ b/server/lib/job-queue/handlers/activitypub-follow.ts | |||
@@ -10,11 +10,13 @@ import { ActorFollowModel } from '../../../models/activitypub/actor-follow' | |||
10 | import { ActorModel } from '../../../models/activitypub/actor' | 10 | import { ActorModel } from '../../../models/activitypub/actor' |
11 | import { Notifier } from '../../notifier' | 11 | import { Notifier } from '../../notifier' |
12 | import { sequelizeTypescript } from '../../../initializers/database' | 12 | import { sequelizeTypescript } from '../../../initializers/database' |
13 | import { MActor, MActorFollowActors, MActorFull } from '../../../typings/models' | ||
13 | 14 | ||
14 | export type ActivitypubFollowPayload = { | 15 | export type ActivitypubFollowPayload = { |
15 | followerActorId: number | 16 | followerActorId: number |
16 | name: string | 17 | name: string |
17 | host: string | 18 | host: string |
19 | isAutoFollow?: boolean | ||
18 | } | 20 | } |
19 | 21 | ||
20 | async function processActivityPubFollow (job: Bull.Job) { | 22 | async function processActivityPubFollow (job: Bull.Job) { |
@@ -23,18 +25,18 @@ async function processActivityPubFollow (job: Bull.Job) { | |||
23 | 25 | ||
24 | logger.info('Processing ActivityPub follow in job %d.', job.id) | 26 | logger.info('Processing ActivityPub follow in job %d.', job.id) |
25 | 27 | ||
26 | let targetActor: ActorModel | 28 | let targetActor: MActorFull |
27 | if (!host || host === WEBSERVER.HOST) { | 29 | if (!host || host === WEBSERVER.HOST) { |
28 | targetActor = await ActorModel.loadLocalByName(payload.name) | 30 | targetActor = await ActorModel.loadLocalByName(payload.name) |
29 | } else { | 31 | } else { |
30 | const sanitizedHost = sanitizeHost(host, REMOTE_SCHEME.HTTP) | 32 | const sanitizedHost = sanitizeHost(host, REMOTE_SCHEME.HTTP) |
31 | const actorUrl = await loadActorUrlOrGetFromWebfinger(payload.name + '@' + sanitizedHost) | 33 | const actorUrl = await loadActorUrlOrGetFromWebfinger(payload.name + '@' + sanitizedHost) |
32 | targetActor = await getOrCreateActorAndServerAndModel(actorUrl) | 34 | targetActor = await getOrCreateActorAndServerAndModel(actorUrl, 'all') |
33 | } | 35 | } |
34 | 36 | ||
35 | const fromActor = await ActorModel.load(payload.followerActorId) | 37 | const fromActor = await ActorModel.load(payload.followerActorId) |
36 | 38 | ||
37 | return retryTransactionWrapper(follow, fromActor, targetActor) | 39 | return retryTransactionWrapper(follow, fromActor, targetActor, payload.isAutoFollow) |
38 | } | 40 | } |
39 | // --------------------------------------------------------------------------- | 41 | // --------------------------------------------------------------------------- |
40 | 42 | ||
@@ -44,7 +46,7 @@ export { | |||
44 | 46 | ||
45 | // --------------------------------------------------------------------------- | 47 | // --------------------------------------------------------------------------- |
46 | 48 | ||
47 | async function follow (fromActor: ActorModel, targetActor: ActorModel) { | 49 | async function follow (fromActor: MActor, targetActor: MActorFull, isAutoFollow = false) { |
48 | if (fromActor.id === targetActor.id) { | 50 | if (fromActor.id === targetActor.id) { |
49 | throw new Error('Follower is the same than target actor.') | 51 | throw new Error('Follower is the same than target actor.') |
50 | } | 52 | } |
@@ -53,7 +55,7 @@ async function follow (fromActor: ActorModel, targetActor: ActorModel) { | |||
53 | const state = !fromActor.serverId && !targetActor.serverId ? 'accepted' : 'pending' | 55 | const state = !fromActor.serverId && !targetActor.serverId ? 'accepted' : 'pending' |
54 | 56 | ||
55 | const actorFollow = await sequelizeTypescript.transaction(async t => { | 57 | const actorFollow = await sequelizeTypescript.transaction(async t => { |
56 | const [ actorFollow ] = await ActorFollowModel.findOrCreate({ | 58 | const [ actorFollow ] = await ActorFollowModel.findOrCreate<MActorFollowActors>({ |
57 | where: { | 59 | where: { |
58 | actorId: fromActor.id, | 60 | actorId: fromActor.id, |
59 | targetActorId: targetActor.id | 61 | targetActorId: targetActor.id |
@@ -74,5 +76,15 @@ async function follow (fromActor: ActorModel, targetActor: ActorModel) { | |||
74 | return actorFollow | 76 | return actorFollow |
75 | }) | 77 | }) |
76 | 78 | ||
77 | if (actorFollow.state === 'accepted') Notifier.Instance.notifyOfNewUserFollow(actorFollow) | 79 | const followerFull = await ActorModel.loadFull(fromActor.id) |
80 | |||
81 | const actorFollowFull = Object.assign(actorFollow, { | ||
82 | ActorFollowing: targetActor, | ||
83 | ActorFollower: followerFull | ||
84 | }) | ||
85 | |||
86 | if (actorFollow.state === 'accepted') Notifier.Instance.notifyOfNewUserFollow(actorFollowFull) | ||
87 | if (isAutoFollow === true) Notifier.Instance.notifyOfAutoInstanceFollowing(actorFollowFull) | ||
88 | |||
89 | return actorFollow | ||
78 | } | 90 | } |
diff --git a/server/lib/job-queue/handlers/activitypub-http-fetcher.ts b/server/lib/job-queue/handlers/activitypub-http-fetcher.ts index c3f59dc77..0182c5169 100644 --- a/server/lib/job-queue/handlers/activitypub-http-fetcher.ts +++ b/server/lib/job-queue/handlers/activitypub-http-fetcher.ts | |||
@@ -11,6 +11,7 @@ import { AccountModel } from '../../../models/account/account' | |||
11 | import { AccountVideoRateModel } from '../../../models/account/account-video-rate' | 11 | import { AccountVideoRateModel } from '../../../models/account/account-video-rate' |
12 | import { VideoShareModel } from '../../../models/video/video-share' | 12 | import { VideoShareModel } from '../../../models/video/video-share' |
13 | import { VideoCommentModel } from '../../../models/video/video-comment' | 13 | import { VideoCommentModel } from '../../../models/video/video-comment' |
14 | import { MAccountDefault, MVideoFullLight } from '../../../typings/models' | ||
14 | 15 | ||
15 | type FetchType = 'activity' | 'video-likes' | 'video-dislikes' | 'video-shares' | 'video-comments' | 'account-playlists' | 16 | type FetchType = 'activity' | 'video-likes' | 'video-dislikes' | 'video-shares' | 'video-comments' | 'account-playlists' |
16 | 17 | ||
@@ -26,10 +27,10 @@ async function processActivityPubHttpFetcher (job: Bull.Job) { | |||
26 | 27 | ||
27 | const payload = job.data as ActivitypubHttpFetcherPayload | 28 | const payload = job.data as ActivitypubHttpFetcherPayload |
28 | 29 | ||
29 | let video: VideoModel | 30 | let video: MVideoFullLight |
30 | if (payload.videoId) video = await VideoModel.loadAndPopulateAccountAndServerAndTags(payload.videoId) | 31 | if (payload.videoId) video = await VideoModel.loadAndPopulateAccountAndServerAndTags(payload.videoId) |
31 | 32 | ||
32 | let account: AccountModel | 33 | let account: MAccountDefault |
33 | if (payload.accountId) account = await AccountModel.load(payload.accountId) | 34 | if (payload.accountId) account = await AccountModel.load(payload.accountId) |
34 | 35 | ||
35 | const fetcherType: { [ id in FetchType ]: (items: any[]) => Promise<any> } = { | 36 | const fetcherType: { [ id in FetchType ]: (items: any[]) => Promise<any> } = { |
diff --git a/server/lib/job-queue/handlers/utils/activitypub-http-utils.ts b/server/lib/job-queue/handlers/utils/activitypub-http-utils.ts index cdee1f6fd..d3bde6e6a 100644 --- a/server/lib/job-queue/handlers/utils/activitypub-http-utils.ts +++ b/server/lib/job-queue/handlers/utils/activitypub-http-utils.ts | |||
@@ -3,6 +3,7 @@ import { getServerActor } from '../../../../helpers/utils' | |||
3 | import { ActorModel } from '../../../../models/activitypub/actor' | 3 | import { ActorModel } from '../../../../models/activitypub/actor' |
4 | import { sha256 } from '../../../../helpers/core-utils' | 4 | import { sha256 } from '../../../../helpers/core-utils' |
5 | import { HTTP_SIGNATURE } from '../../../../initializers/constants' | 5 | import { HTTP_SIGNATURE } from '../../../../initializers/constants' |
6 | import { MActor } from '../../../../typings/models' | ||
6 | 7 | ||
7 | type Payload = { body: any, signatureActorId?: number } | 8 | type Payload = { body: any, signatureActorId?: number } |
8 | 9 | ||
@@ -19,7 +20,8 @@ async function computeBody (payload: Payload) { | |||
19 | } | 20 | } |
20 | 21 | ||
21 | async function buildSignedRequestOptions (payload: Payload) { | 22 | async function buildSignedRequestOptions (payload: Payload) { |
22 | let actor: ActorModel | null | 23 | let actor: MActor | null |
24 | |||
23 | if (payload.signatureActorId) { | 25 | if (payload.signatureActorId) { |
24 | actor = await ActorModel.load(payload.signatureActorId) | 26 | actor = await ActorModel.load(payload.signatureActorId) |
25 | if (!actor) throw new Error('Unknown signature actor id.') | 27 | if (!actor) throw new Error('Unknown signature actor id.') |
diff --git a/server/lib/job-queue/handlers/video-file-import.ts b/server/lib/job-queue/handlers/video-file-import.ts index 8cacb0ef3..5c5b7dccb 100644 --- a/server/lib/job-queue/handlers/video-file-import.ts +++ b/server/lib/job-queue/handlers/video-file-import.ts | |||
@@ -6,6 +6,7 @@ import { getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffmpeg | |||
6 | import { copy, stat } from 'fs-extra' | 6 | import { copy, stat } from 'fs-extra' |
7 | import { VideoFileModel } from '../../../models/video/video-file' | 7 | import { VideoFileModel } from '../../../models/video/video-file' |
8 | import { extname } from 'path' | 8 | import { extname } from 'path' |
9 | import { MVideoFile, MVideoWithFile } from '@server/typings/models' | ||
9 | 10 | ||
10 | export type VideoFileImportPayload = { | 11 | export type VideoFileImportPayload = { |
11 | videoUUID: string, | 12 | videoUUID: string, |
@@ -37,7 +38,7 @@ export { | |||
37 | 38 | ||
38 | // --------------------------------------------------------------------------- | 39 | // --------------------------------------------------------------------------- |
39 | 40 | ||
40 | async function updateVideoFile (video: VideoModel, inputFilePath: string) { | 41 | async function updateVideoFile (video: MVideoWithFile, inputFilePath: string) { |
41 | const { videoFileResolution } = await getVideoFileResolution(inputFilePath) | 42 | const { videoFileResolution } = await getVideoFileResolution(inputFilePath) |
42 | const { size } = await stat(inputFilePath) | 43 | const { size } = await stat(inputFilePath) |
43 | const fps = await getVideoFileFPS(inputFilePath) | 44 | const fps = await getVideoFileFPS(inputFilePath) |
@@ -48,7 +49,7 @@ async function updateVideoFile (video: VideoModel, inputFilePath: string) { | |||
48 | size, | 49 | size, |
49 | fps, | 50 | fps, |
50 | videoId: video.id | 51 | videoId: video.id |
51 | }) | 52 | }) as MVideoFile |
52 | 53 | ||
53 | const currentVideoFile = video.VideoFiles.find(videoFile => videoFile.resolution === updatedVideoFile.resolution) | 54 | const currentVideoFile = video.VideoFiles.find(videoFile => videoFile.resolution === updatedVideoFile.resolution) |
54 | 55 | ||
@@ -60,9 +61,9 @@ async function updateVideoFile (video: VideoModel, inputFilePath: string) { | |||
60 | video.VideoFiles = video.VideoFiles.filter(f => f !== currentVideoFile) | 61 | video.VideoFiles = video.VideoFiles.filter(f => f !== currentVideoFile) |
61 | 62 | ||
62 | // Update the database | 63 | // Update the database |
63 | currentVideoFile.set('extname', updatedVideoFile.extname) | 64 | currentVideoFile.extname = updatedVideoFile.extname |
64 | currentVideoFile.set('size', updatedVideoFile.size) | 65 | currentVideoFile.size = updatedVideoFile.size |
65 | currentVideoFile.set('fps', updatedVideoFile.fps) | 66 | currentVideoFile.fps = updatedVideoFile.fps |
66 | 67 | ||
67 | updatedVideoFile = currentVideoFile | 68 | updatedVideoFile = currentVideoFile |
68 | } | 69 | } |
diff --git a/server/lib/job-queue/handlers/video-import.ts b/server/lib/job-queue/handlers/video-import.ts index 13b741180..93a3e9d90 100644 --- a/server/lib/job-queue/handlers/video-import.ts +++ b/server/lib/job-queue/handlers/video-import.ts | |||
@@ -17,9 +17,11 @@ import { move, remove, stat } from 'fs-extra' | |||
17 | import { Notifier } from '../../notifier' | 17 | import { Notifier } from '../../notifier' |
18 | import { CONFIG } from '../../../initializers/config' | 18 | import { CONFIG } from '../../../initializers/config' |
19 | import { sequelizeTypescript } from '../../../initializers/database' | 19 | import { sequelizeTypescript } from '../../../initializers/database' |
20 | import { ThumbnailModel } from '../../../models/video/thumbnail' | ||
21 | import { createVideoMiniatureFromUrl, generateVideoMiniature } from '../../thumbnail' | 20 | import { createVideoMiniatureFromUrl, generateVideoMiniature } from '../../thumbnail' |
22 | import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type' | 21 | import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type' |
22 | import { MThumbnail } from '../../../typings/models/video/thumbnail' | ||
23 | import { MVideoImportDefault, MVideoImportDefaultFiles, MVideoImportVideo } from '@server/typings/models/video/video-import' | ||
24 | import { MVideoBlacklistVideo, MVideoBlacklist } from '@server/typings/models' | ||
23 | 25 | ||
24 | type VideoImportYoutubeDLPayload = { | 26 | type VideoImportYoutubeDLPayload = { |
25 | type: 'youtube-dl' | 27 | type: 'youtube-dl' |
@@ -110,7 +112,7 @@ type ProcessFileOptions = { | |||
110 | generateThumbnail: boolean | 112 | generateThumbnail: boolean |
111 | generatePreview: boolean | 113 | generatePreview: boolean |
112 | } | 114 | } |
113 | async function processFile (downloader: () => Promise<string>, videoImport: VideoImportModel, options: ProcessFileOptions) { | 115 | async function processFile (downloader: () => Promise<string>, videoImport: MVideoImportDefault, options: ProcessFileOptions) { |
114 | let tempVideoPath: string | 116 | let tempVideoPath: string |
115 | let videoDestFile: string | 117 | let videoDestFile: string |
116 | let videoFile: VideoFileModel | 118 | let videoFile: VideoFileModel |
@@ -139,41 +141,44 @@ async function processFile (downloader: () => Promise<string>, videoImport: Vide | |||
139 | videoId: videoImport.videoId | 141 | videoId: videoImport.videoId |
140 | } | 142 | } |
141 | videoFile = new VideoFileModel(videoFileData) | 143 | videoFile = new VideoFileModel(videoFileData) |
144 | |||
145 | const videoWithFiles = Object.assign(videoImport.Video, { VideoFiles: [ videoFile ] }) | ||
142 | // To clean files if the import fails | 146 | // To clean files if the import fails |
143 | videoImport.Video.VideoFiles = [ videoFile ] | 147 | const videoImportWithFiles: MVideoImportDefaultFiles = Object.assign(videoImport, { Video: videoWithFiles }) |
144 | 148 | ||
145 | // Move file | 149 | // Move file |
146 | videoDestFile = join(CONFIG.STORAGE.VIDEOS_DIR, videoImport.Video.getVideoFilename(videoFile)) | 150 | videoDestFile = join(CONFIG.STORAGE.VIDEOS_DIR, videoImportWithFiles.Video.getVideoFilename(videoFile)) |
147 | await move(tempVideoPath, videoDestFile) | 151 | await move(tempVideoPath, videoDestFile) |
148 | tempVideoPath = null // This path is not used anymore | 152 | tempVideoPath = null // This path is not used anymore |
149 | 153 | ||
150 | // Process thumbnail | 154 | // Process thumbnail |
151 | let thumbnailModel: ThumbnailModel | 155 | let thumbnailModel: MThumbnail |
152 | if (options.downloadThumbnail && options.thumbnailUrl) { | 156 | if (options.downloadThumbnail && options.thumbnailUrl) { |
153 | thumbnailModel = await createVideoMiniatureFromUrl(options.thumbnailUrl, videoImport.Video, ThumbnailType.MINIATURE) | 157 | thumbnailModel = await createVideoMiniatureFromUrl(options.thumbnailUrl, videoImportWithFiles.Video, ThumbnailType.MINIATURE) |
154 | } else if (options.generateThumbnail || options.downloadThumbnail) { | 158 | } else if (options.generateThumbnail || options.downloadThumbnail) { |
155 | thumbnailModel = await generateVideoMiniature(videoImport.Video, videoFile, ThumbnailType.MINIATURE) | 159 | thumbnailModel = await generateVideoMiniature(videoImportWithFiles.Video, videoFile, ThumbnailType.MINIATURE) |
156 | } | 160 | } |
157 | 161 | ||
158 | // Process preview | 162 | // Process preview |
159 | let previewModel: ThumbnailModel | 163 | let previewModel: MThumbnail |
160 | if (options.downloadPreview && options.thumbnailUrl) { | 164 | if (options.downloadPreview && options.thumbnailUrl) { |
161 | previewModel = await createVideoMiniatureFromUrl(options.thumbnailUrl, videoImport.Video, ThumbnailType.PREVIEW) | 165 | previewModel = await createVideoMiniatureFromUrl(options.thumbnailUrl, videoImportWithFiles.Video, ThumbnailType.PREVIEW) |
162 | } else if (options.generatePreview || options.downloadPreview) { | 166 | } else if (options.generatePreview || options.downloadPreview) { |
163 | previewModel = await generateVideoMiniature(videoImport.Video, videoFile, ThumbnailType.PREVIEW) | 167 | previewModel = await generateVideoMiniature(videoImportWithFiles.Video, videoFile, ThumbnailType.PREVIEW) |
164 | } | 168 | } |
165 | 169 | ||
166 | // Create torrent | 170 | // Create torrent |
167 | await videoImport.Video.createTorrentAndSetInfoHash(videoFile) | 171 | await videoImportWithFiles.Video.createTorrentAndSetInfoHash(videoFile) |
172 | |||
173 | const { videoImportUpdated, video } = await sequelizeTypescript.transaction(async t => { | ||
174 | const videoImportToUpdate = videoImportWithFiles as MVideoImportVideo | ||
168 | 175 | ||
169 | const videoImportUpdated: VideoImportModel = await sequelizeTypescript.transaction(async t => { | ||
170 | // Refresh video | 176 | // Refresh video |
171 | const video = await VideoModel.load(videoImport.videoId, t) | 177 | const video = await VideoModel.load(videoImportToUpdate.videoId, t) |
172 | if (!video) throw new Error('Video linked to import ' + videoImport.videoId + ' does not exist anymore.') | 178 | if (!video) throw new Error('Video linked to import ' + videoImportToUpdate.videoId + ' does not exist anymore.') |
173 | videoImport.Video = video | ||
174 | 179 | ||
175 | const videoFileCreated = await videoFile.save({ transaction: t }) | 180 | const videoFileCreated = await videoFile.save({ transaction: t }) |
176 | video.VideoFiles = [ videoFileCreated ] | 181 | videoImportToUpdate.Video = Object.assign(video, { VideoFiles: [ videoFileCreated ] }) |
177 | 182 | ||
178 | // Update video DB object | 183 | // Update video DB object |
179 | video.duration = duration | 184 | video.duration = duration |
@@ -188,25 +193,27 @@ async function processFile (downloader: () => Promise<string>, videoImport: Vide | |||
188 | await federateVideoIfNeeded(videoForFederation, true, t) | 193 | await federateVideoIfNeeded(videoForFederation, true, t) |
189 | 194 | ||
190 | // Update video import object | 195 | // Update video import object |
191 | videoImport.state = VideoImportState.SUCCESS | 196 | videoImportToUpdate.state = VideoImportState.SUCCESS |
192 | const videoImportUpdated = await videoImport.save({ transaction: t }) | 197 | const videoImportUpdated = await videoImportToUpdate.save({ transaction: t }) as MVideoImportVideo |
198 | videoImportUpdated.Video = video | ||
193 | 199 | ||
194 | logger.info('Video %s imported.', video.uuid) | 200 | logger.info('Video %s imported.', video.uuid) |
195 | 201 | ||
196 | videoImportUpdated.Video = videoForFederation | 202 | return { videoImportUpdated, video: videoForFederation } |
197 | return videoImportUpdated | ||
198 | }) | 203 | }) |
199 | 204 | ||
200 | Notifier.Instance.notifyOnFinishedVideoImport(videoImportUpdated, true) | 205 | Notifier.Instance.notifyOnFinishedVideoImport(videoImportUpdated, true) |
201 | 206 | ||
202 | if (videoImportUpdated.Video.isBlacklisted()) { | 207 | if (video.isBlacklisted()) { |
203 | Notifier.Instance.notifyOnVideoAutoBlacklist(videoImportUpdated.Video) | 208 | const videoBlacklist = Object.assign(video.VideoBlacklist, { Video: video }) |
209 | |||
210 | Notifier.Instance.notifyOnVideoAutoBlacklist(videoBlacklist) | ||
204 | } else { | 211 | } else { |
205 | Notifier.Instance.notifyOnNewVideoIfNeeded(videoImportUpdated.Video) | 212 | Notifier.Instance.notifyOnNewVideoIfNeeded(video) |
206 | } | 213 | } |
207 | 214 | ||
208 | // Create transcoding jobs? | 215 | // Create transcoding jobs? |
209 | if (videoImportUpdated.Video.state === VideoState.TO_TRANSCODE) { | 216 | if (video.state === VideoState.TO_TRANSCODE) { |
210 | // Put uuid because we don't have id auto incremented for now | 217 | // Put uuid because we don't have id auto incremented for now |
211 | const dataInput = { | 218 | const dataInput = { |
212 | type: 'optimize' as 'optimize', | 219 | type: 'optimize' as 'optimize', |
diff --git a/server/lib/job-queue/handlers/video-transcoding.ts b/server/lib/job-queue/handlers/video-transcoding.ts index 981daf9a1..2ebe15bcb 100644 --- a/server/lib/job-queue/handlers/video-transcoding.ts +++ b/server/lib/job-queue/handlers/video-transcoding.ts | |||
@@ -11,6 +11,7 @@ import { computeResolutionsToTranscode } from '../../../helpers/ffmpeg-utils' | |||
11 | import { generateHlsPlaylist, optimizeVideofile, transcodeOriginalVideofile, mergeAudioVideofile } from '../../video-transcoding' | 11 | import { generateHlsPlaylist, optimizeVideofile, transcodeOriginalVideofile, mergeAudioVideofile } from '../../video-transcoding' |
12 | import { Notifier } from '../../notifier' | 12 | import { Notifier } from '../../notifier' |
13 | import { CONFIG } from '../../../initializers/config' | 13 | import { CONFIG } from '../../../initializers/config' |
14 | import { MVideoUUID, MVideoWithFile } from '@server/typings/models' | ||
14 | 15 | ||
15 | interface BaseTranscodingPayload { | 16 | interface BaseTranscodingPayload { |
16 | videoUUID: string | 17 | videoUUID: string |
@@ -73,7 +74,7 @@ async function processVideoTranscoding (job: Bull.Job) { | |||
73 | return video | 74 | return video |
74 | } | 75 | } |
75 | 76 | ||
76 | async function onHlsPlaylistGenerationSuccess (video: VideoModel) { | 77 | async function onHlsPlaylistGenerationSuccess (video: MVideoUUID) { |
77 | if (video === undefined) return undefined | 78 | if (video === undefined) return undefined |
78 | 79 | ||
79 | await sequelizeTypescript.transaction(async t => { | 80 | await sequelizeTypescript.transaction(async t => { |
@@ -87,7 +88,7 @@ async function onHlsPlaylistGenerationSuccess (video: VideoModel) { | |||
87 | }) | 88 | }) |
88 | } | 89 | } |
89 | 90 | ||
90 | async function publishNewResolutionIfNeeded (video: VideoModel, payload?: NewResolutionTranscodingPayload | MergeAudioTranscodingPayload) { | 91 | async function publishNewResolutionIfNeeded (video: MVideoUUID, payload?: NewResolutionTranscodingPayload | MergeAudioTranscodingPayload) { |
91 | const { videoDatabase, videoPublished } = await sequelizeTypescript.transaction(async t => { | 92 | const { videoDatabase, videoPublished } = await sequelizeTypescript.transaction(async t => { |
92 | // Maybe the video changed in database, refresh it | 93 | // Maybe the video changed in database, refresh it |
93 | let videoDatabase = await VideoModel.loadAndPopulateAccountAndServerAndTags(video.uuid, t) | 94 | let videoDatabase = await VideoModel.loadAndPopulateAccountAndServerAndTags(video.uuid, t) |
@@ -119,7 +120,7 @@ async function publishNewResolutionIfNeeded (video: VideoModel, payload?: NewRes | |||
119 | await createHlsJobIfEnabled(payload) | 120 | await createHlsJobIfEnabled(payload) |
120 | } | 121 | } |
121 | 122 | ||
122 | async function onVideoFileOptimizerSuccess (videoArg: VideoModel, payload: OptimizeTranscodingPayload) { | 123 | async function onVideoFileOptimizerSuccess (videoArg: MVideoWithFile, payload: OptimizeTranscodingPayload) { |
123 | if (videoArg === undefined) return undefined | 124 | if (videoArg === undefined) return undefined |
124 | 125 | ||
125 | // Outside the transaction (IO on disk) | 126 | // Outside the transaction (IO on disk) |
diff --git a/server/lib/notifier.ts b/server/lib/notifier.ts index a7dfb0979..b7cc2607d 100644 --- a/server/lib/notifier.ts +++ b/server/lib/notifier.ts | |||
@@ -1,20 +1,30 @@ | |||
1 | import { UserNotificationSettingValue, UserNotificationType, UserRight } from '../../shared/models/users' | 1 | import { UserNotificationSettingValue, UserNotificationType, UserRight } from '../../shared/models/users' |
2 | import { logger } from '../helpers/logger' | 2 | import { logger } from '../helpers/logger' |
3 | import { VideoModel } from '../models/video/video' | ||
4 | import { Emailer } from './emailer' | 3 | import { Emailer } from './emailer' |
5 | import { UserNotificationModel } from '../models/account/user-notification' | 4 | import { UserNotificationModel } from '../models/account/user-notification' |
6 | import { VideoCommentModel } from '../models/video/video-comment' | ||
7 | import { UserModel } from '../models/account/user' | 5 | import { UserModel } from '../models/account/user' |
8 | import { PeerTubeSocket } from './peertube-socket' | 6 | import { PeerTubeSocket } from './peertube-socket' |
9 | import { CONFIG } from '../initializers/config' | 7 | import { CONFIG } from '../initializers/config' |
10 | import { VideoPrivacy, VideoState } from '../../shared/models/videos' | 8 | import { VideoPrivacy, VideoState } from '../../shared/models/videos' |
11 | import { VideoAbuseModel } from '../models/video/video-abuse' | ||
12 | import { VideoBlacklistModel } from '../models/video/video-blacklist' | ||
13 | import * as Bluebird from 'bluebird' | 9 | import * as Bluebird from 'bluebird' |
14 | import { VideoImportModel } from '../models/video/video-import' | ||
15 | import { AccountBlocklistModel } from '../models/account/account-blocklist' | 10 | import { AccountBlocklistModel } from '../models/account/account-blocklist' |
16 | import { ActorFollowModel } from '../models/activitypub/actor-follow' | 11 | import { |
17 | import { AccountModel } from '../models/account/account' | 12 | MCommentOwnerVideo, |
13 | MVideoAbuseVideo, | ||
14 | MVideoAccountLight, | ||
15 | MVideoBlacklistLightVideo, | ||
16 | MVideoBlacklistVideo, | ||
17 | MVideoFullLight | ||
18 | } from '../typings/models/video' | ||
19 | import { | ||
20 | MUser, | ||
21 | MUserDefault, | ||
22 | MUserNotifSettingAccount, | ||
23 | MUserWithNotificationSetting, | ||
24 | UserNotificationModelForApi | ||
25 | } from '@server/typings/models/user' | ||
26 | import { MActorFollowFull } from '../typings/models' | ||
27 | import { MVideoImportVideo } from '@server/typings/models/video/video-import' | ||
18 | 28 | ||
19 | class Notifier { | 29 | class Notifier { |
20 | 30 | ||
@@ -22,7 +32,7 @@ class Notifier { | |||
22 | 32 | ||
23 | private constructor () {} | 33 | private constructor () {} |
24 | 34 | ||
25 | notifyOnNewVideoIfNeeded (video: VideoModel): void { | 35 | notifyOnNewVideoIfNeeded (video: MVideoAccountLight): void { |
26 | // Only notify on public and published videos which are not blacklisted | 36 | // Only notify on public and published videos which are not blacklisted |
27 | if (video.privacy !== VideoPrivacy.PUBLIC || video.state !== VideoState.PUBLISHED || video.isBlacklisted()) return | 37 | if (video.privacy !== VideoPrivacy.PUBLIC || video.state !== VideoState.PUBLISHED || video.isBlacklisted()) return |
28 | 38 | ||
@@ -30,7 +40,7 @@ class Notifier { | |||
30 | .catch(err => logger.error('Cannot notify subscribers of new video %s.', video.url, { err })) | 40 | .catch(err => logger.error('Cannot notify subscribers of new video %s.', video.url, { err })) |
31 | } | 41 | } |
32 | 42 | ||
33 | notifyOnVideoPublishedAfterTranscoding (video: VideoModel): void { | 43 | notifyOnVideoPublishedAfterTranscoding (video: MVideoFullLight): void { |
34 | // don't notify if didn't wait for transcoding or video is still blacklisted/waiting for scheduled update | 44 | // don't notify if didn't wait for transcoding or video is still blacklisted/waiting for scheduled update |
35 | if (!video.waitTranscoding || video.VideoBlacklist || video.ScheduleVideoUpdate) return | 45 | if (!video.waitTranscoding || video.VideoBlacklist || video.ScheduleVideoUpdate) return |
36 | 46 | ||
@@ -38,7 +48,7 @@ class Notifier { | |||
38 | .catch(err => logger.error('Cannot notify owner that its video %s has been published after transcoding.', video.url, { err })) | 48 | .catch(err => logger.error('Cannot notify owner that its video %s has been published after transcoding.', video.url, { err })) |
39 | } | 49 | } |
40 | 50 | ||
41 | notifyOnVideoPublishedAfterScheduledUpdate (video: VideoModel): void { | 51 | notifyOnVideoPublishedAfterScheduledUpdate (video: MVideoFullLight): void { |
42 | // don't notify if video is still blacklisted or waiting for transcoding | 52 | // don't notify if video is still blacklisted or waiting for transcoding |
43 | if (video.VideoBlacklist || (video.waitTranscoding && video.state !== VideoState.PUBLISHED)) return | 53 | if (video.VideoBlacklist || (video.waitTranscoding && video.state !== VideoState.PUBLISHED)) return |
44 | 54 | ||
@@ -46,7 +56,7 @@ class Notifier { | |||
46 | .catch(err => logger.error('Cannot notify owner that its video %s has been published after scheduled update.', video.url, { err })) | 56 | .catch(err => logger.error('Cannot notify owner that its video %s has been published after scheduled update.', video.url, { err })) |
47 | } | 57 | } |
48 | 58 | ||
49 | notifyOnVideoPublishedAfterRemovedFromAutoBlacklist (video: VideoModel): void { | 59 | notifyOnVideoPublishedAfterRemovedFromAutoBlacklist (video: MVideoFullLight): void { |
50 | // don't notify if video is still waiting for transcoding or scheduled update | 60 | // don't notify if video is still waiting for transcoding or scheduled update |
51 | if (video.ScheduleVideoUpdate || (video.waitTranscoding && video.state !== VideoState.PUBLISHED)) return | 61 | if (video.ScheduleVideoUpdate || (video.waitTranscoding && video.state !== VideoState.PUBLISHED)) return |
52 | 62 | ||
@@ -54,7 +64,7 @@ class Notifier { | |||
54 | .catch(err => logger.error('Cannot notify owner that its video %s has been published after removed from auto-blacklist.', video.url, { err })) // tslint:disable-line:max-line-length | 64 | .catch(err => logger.error('Cannot notify owner that its video %s has been published after removed from auto-blacklist.', video.url, { err })) // tslint:disable-line:max-line-length |
55 | } | 65 | } |
56 | 66 | ||
57 | notifyOnNewComment (comment: VideoCommentModel): void { | 67 | notifyOnNewComment (comment: MCommentOwnerVideo): void { |
58 | this.notifyVideoOwnerOfNewComment(comment) | 68 | this.notifyVideoOwnerOfNewComment(comment) |
59 | .catch(err => logger.error('Cannot notify video owner of new comment %s.', comment.url, { err })) | 69 | .catch(err => logger.error('Cannot notify video owner of new comment %s.', comment.url, { err })) |
60 | 70 | ||
@@ -62,37 +72,37 @@ class Notifier { | |||
62 | .catch(err => logger.error('Cannot notify mentions of comment %s.', comment.url, { err })) | 72 | .catch(err => logger.error('Cannot notify mentions of comment %s.', comment.url, { err })) |
63 | } | 73 | } |
64 | 74 | ||
65 | notifyOnNewVideoAbuse (videoAbuse: VideoAbuseModel): void { | 75 | notifyOnNewVideoAbuse (videoAbuse: MVideoAbuseVideo): void { |
66 | this.notifyModeratorsOfNewVideoAbuse(videoAbuse) | 76 | this.notifyModeratorsOfNewVideoAbuse(videoAbuse) |
67 | .catch(err => logger.error('Cannot notify of new video abuse of video %s.', videoAbuse.Video.url, { err })) | 77 | .catch(err => logger.error('Cannot notify of new video abuse of video %s.', videoAbuse.Video.url, { err })) |
68 | } | 78 | } |
69 | 79 | ||
70 | notifyOnVideoAutoBlacklist (video: VideoModel): void { | 80 | notifyOnVideoAutoBlacklist (videoBlacklist: MVideoBlacklistLightVideo): void { |
71 | this.notifyModeratorsOfVideoAutoBlacklist(video) | 81 | this.notifyModeratorsOfVideoAutoBlacklist(videoBlacklist) |
72 | .catch(err => logger.error('Cannot notify of auto-blacklist of video %s.', video.url, { err })) | 82 | .catch(err => logger.error('Cannot notify of auto-blacklist of video %s.', videoBlacklist.Video.url, { err })) |
73 | } | 83 | } |
74 | 84 | ||
75 | notifyOnVideoBlacklist (videoBlacklist: VideoBlacklistModel): void { | 85 | notifyOnVideoBlacklist (videoBlacklist: MVideoBlacklistVideo): void { |
76 | this.notifyVideoOwnerOfBlacklist(videoBlacklist) | 86 | this.notifyVideoOwnerOfBlacklist(videoBlacklist) |
77 | .catch(err => logger.error('Cannot notify video owner of new video blacklist of %s.', videoBlacklist.Video.url, { err })) | 87 | .catch(err => logger.error('Cannot notify video owner of new video blacklist of %s.', videoBlacklist.Video.url, { err })) |
78 | } | 88 | } |
79 | 89 | ||
80 | notifyOnVideoUnblacklist (video: VideoModel): void { | 90 | notifyOnVideoUnblacklist (video: MVideoFullLight): void { |
81 | this.notifyVideoOwnerOfUnblacklist(video) | 91 | this.notifyVideoOwnerOfUnblacklist(video) |
82 | .catch(err => logger.error('Cannot notify video owner of unblacklist of %s.', video.url, { err })) | 92 | .catch(err => logger.error('Cannot notify video owner of unblacklist of %s.', video.url, { err })) |
83 | } | 93 | } |
84 | 94 | ||
85 | notifyOnFinishedVideoImport (videoImport: VideoImportModel, success: boolean): void { | 95 | notifyOnFinishedVideoImport (videoImport: MVideoImportVideo, success: boolean): void { |
86 | this.notifyOwnerVideoImportIsFinished(videoImport, success) | 96 | this.notifyOwnerVideoImportIsFinished(videoImport, success) |
87 | .catch(err => logger.error('Cannot notify owner that its video import %s is finished.', videoImport.getTargetIdentifier(), { err })) | 97 | .catch(err => logger.error('Cannot notify owner that its video import %s is finished.', videoImport.getTargetIdentifier(), { err })) |
88 | } | 98 | } |
89 | 99 | ||
90 | notifyOnNewUserRegistration (user: UserModel): void { | 100 | notifyOnNewUserRegistration (user: MUserDefault): void { |
91 | this.notifyModeratorsOfNewUserRegistration(user) | 101 | this.notifyModeratorsOfNewUserRegistration(user) |
92 | .catch(err => logger.error('Cannot notify moderators of new user registration (%s).', user.username, { err })) | 102 | .catch(err => logger.error('Cannot notify moderators of new user registration (%s).', user.username, { err })) |
93 | } | 103 | } |
94 | 104 | ||
95 | notifyOfNewUserFollow (actorFollow: ActorFollowModel): void { | 105 | notifyOfNewUserFollow (actorFollow: MActorFollowFull): void { |
96 | this.notifyUserOfNewActorFollow(actorFollow) | 106 | this.notifyUserOfNewActorFollow(actorFollow) |
97 | .catch(err => { | 107 | .catch(err => { |
98 | logger.error( | 108 | logger.error( |
@@ -104,25 +114,32 @@ class Notifier { | |||
104 | }) | 114 | }) |
105 | } | 115 | } |
106 | 116 | ||
107 | notifyOfNewInstanceFollow (actorFollow: ActorFollowModel): void { | 117 | notifyOfNewInstanceFollow (actorFollow: MActorFollowFull): void { |
108 | this.notifyAdminsOfNewInstanceFollow(actorFollow) | 118 | this.notifyAdminsOfNewInstanceFollow(actorFollow) |
109 | .catch(err => { | 119 | .catch(err => { |
110 | logger.error('Cannot notify administrators of new follower %s.', actorFollow.ActorFollower.url, { err }) | 120 | logger.error('Cannot notify administrators of new follower %s.', actorFollow.ActorFollower.url, { err }) |
111 | }) | 121 | }) |
112 | } | 122 | } |
113 | 123 | ||
114 | private async notifySubscribersOfNewVideo (video: VideoModel) { | 124 | notifyOfAutoInstanceFollowing (actorFollow: MActorFollowFull): void { |
125 | this.notifyAdminsOfAutoInstanceFollowing(actorFollow) | ||
126 | .catch(err => { | ||
127 | logger.error('Cannot notify administrators of auto instance following %s.', actorFollow.ActorFollowing.url, { err }) | ||
128 | }) | ||
129 | } | ||
130 | |||
131 | private async notifySubscribersOfNewVideo (video: MVideoAccountLight) { | ||
115 | // List all followers that are users | 132 | // List all followers that are users |
116 | const users = await UserModel.listUserSubscribersOf(video.VideoChannel.actorId) | 133 | const users = await UserModel.listUserSubscribersOf(video.VideoChannel.actorId) |
117 | 134 | ||
118 | logger.info('Notifying %d users of new video %s.', users.length, video.url) | 135 | logger.info('Notifying %d users of new video %s.', users.length, video.url) |
119 | 136 | ||
120 | function settingGetter (user: UserModel) { | 137 | function settingGetter (user: MUserWithNotificationSetting) { |
121 | return user.NotificationSetting.newVideoFromSubscription | 138 | return user.NotificationSetting.newVideoFromSubscription |
122 | } | 139 | } |
123 | 140 | ||
124 | async function notificationCreator (user: UserModel) { | 141 | async function notificationCreator (user: MUserWithNotificationSetting) { |
125 | const notification = await UserNotificationModel.create({ | 142 | const notification = await UserNotificationModel.create<UserNotificationModelForApi>({ |
126 | type: UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION, | 143 | type: UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION, |
127 | userId: user.id, | 144 | userId: user.id, |
128 | videoId: video.id | 145 | videoId: video.id |
@@ -139,7 +156,7 @@ class Notifier { | |||
139 | return this.notify({ users, settingGetter, notificationCreator, emailSender }) | 156 | return this.notify({ users, settingGetter, notificationCreator, emailSender }) |
140 | } | 157 | } |
141 | 158 | ||
142 | private async notifyVideoOwnerOfNewComment (comment: VideoCommentModel) { | 159 | private async notifyVideoOwnerOfNewComment (comment: MCommentOwnerVideo) { |
143 | if (comment.Video.isOwned() === false) return | 160 | if (comment.Video.isOwned() === false) return |
144 | 161 | ||
145 | const user = await UserModel.loadByVideoId(comment.videoId) | 162 | const user = await UserModel.loadByVideoId(comment.videoId) |
@@ -152,12 +169,12 @@ class Notifier { | |||
152 | 169 | ||
153 | logger.info('Notifying user %s of new comment %s.', user.username, comment.url) | 170 | logger.info('Notifying user %s of new comment %s.', user.username, comment.url) |
154 | 171 | ||
155 | function settingGetter (user: UserModel) { | 172 | function settingGetter (user: MUserWithNotificationSetting) { |
156 | return user.NotificationSetting.newCommentOnMyVideo | 173 | return user.NotificationSetting.newCommentOnMyVideo |
157 | } | 174 | } |
158 | 175 | ||
159 | async function notificationCreator (user: UserModel) { | 176 | async function notificationCreator (user: MUserWithNotificationSetting) { |
160 | const notification = await UserNotificationModel.create({ | 177 | const notification = await UserNotificationModel.create<UserNotificationModelForApi>({ |
161 | type: UserNotificationType.NEW_COMMENT_ON_MY_VIDEO, | 178 | type: UserNotificationType.NEW_COMMENT_ON_MY_VIDEO, |
162 | userId: user.id, | 179 | userId: user.id, |
163 | commentId: comment.id | 180 | commentId: comment.id |
@@ -174,7 +191,7 @@ class Notifier { | |||
174 | return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender }) | 191 | return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender }) |
175 | } | 192 | } |
176 | 193 | ||
177 | private async notifyOfCommentMention (comment: VideoCommentModel) { | 194 | private async notifyOfCommentMention (comment: MCommentOwnerVideo) { |
178 | const extractedUsernames = comment.extractMentions() | 195 | const extractedUsernames = comment.extractMentions() |
179 | logger.debug( | 196 | logger.debug( |
180 | 'Extracted %d username from comment %s.', extractedUsernames.length, comment.url, | 197 | 'Extracted %d username from comment %s.', extractedUsernames.length, comment.url, |
@@ -197,14 +214,14 @@ class Notifier { | |||
197 | 214 | ||
198 | logger.info('Notifying %d users of new comment %s.', users.length, comment.url) | 215 | logger.info('Notifying %d users of new comment %s.', users.length, comment.url) |
199 | 216 | ||
200 | function settingGetter (user: UserModel) { | 217 | function settingGetter (user: MUserNotifSettingAccount) { |
201 | if (accountMutedHash[user.Account.id] === true) return UserNotificationSettingValue.NONE | 218 | if (accountMutedHash[user.Account.id] === true) return UserNotificationSettingValue.NONE |
202 | 219 | ||
203 | return user.NotificationSetting.commentMention | 220 | return user.NotificationSetting.commentMention |
204 | } | 221 | } |
205 | 222 | ||
206 | async function notificationCreator (user: UserModel) { | 223 | async function notificationCreator (user: MUserNotifSettingAccount) { |
207 | const notification = await UserNotificationModel.create({ | 224 | const notification = await UserNotificationModel.create<UserNotificationModelForApi>({ |
208 | type: UserNotificationType.COMMENT_MENTION, | 225 | type: UserNotificationType.COMMENT_MENTION, |
209 | userId: user.id, | 226 | userId: user.id, |
210 | commentId: comment.id | 227 | commentId: comment.id |
@@ -221,7 +238,7 @@ class Notifier { | |||
221 | return this.notify({ users, settingGetter, notificationCreator, emailSender }) | 238 | return this.notify({ users, settingGetter, notificationCreator, emailSender }) |
222 | } | 239 | } |
223 | 240 | ||
224 | private async notifyUserOfNewActorFollow (actorFollow: ActorFollowModel) { | 241 | private async notifyUserOfNewActorFollow (actorFollow: MActorFollowFull) { |
225 | if (actorFollow.ActorFollowing.isOwned() === false) return | 242 | if (actorFollow.ActorFollowing.isOwned() === false) return |
226 | 243 | ||
227 | // Account follows one of our account? | 244 | // Account follows one of our account? |
@@ -236,9 +253,6 @@ class Notifier { | |||
236 | 253 | ||
237 | if (!user) return | 254 | if (!user) return |
238 | 255 | ||
239 | if (!actorFollow.ActorFollower.Account || !actorFollow.ActorFollower.Account.name) { | ||
240 | actorFollow.ActorFollower.Account = await actorFollow.ActorFollower.$get('Account') as AccountModel | ||
241 | } | ||
242 | const followerAccount = actorFollow.ActorFollower.Account | 256 | const followerAccount = actorFollow.ActorFollower.Account |
243 | 257 | ||
244 | const accountMuted = await AccountBlocklistModel.isAccountMutedBy(user.Account.id, followerAccount.id) | 258 | const accountMuted = await AccountBlocklistModel.isAccountMutedBy(user.Account.id, followerAccount.id) |
@@ -246,12 +260,12 @@ class Notifier { | |||
246 | 260 | ||
247 | logger.info('Notifying user %s of new follower: %s.', user.username, followerAccount.getDisplayName()) | 261 | logger.info('Notifying user %s of new follower: %s.', user.username, followerAccount.getDisplayName()) |
248 | 262 | ||
249 | function settingGetter (user: UserModel) { | 263 | function settingGetter (user: MUserWithNotificationSetting) { |
250 | return user.NotificationSetting.newFollow | 264 | return user.NotificationSetting.newFollow |
251 | } | 265 | } |
252 | 266 | ||
253 | async function notificationCreator (user: UserModel) { | 267 | async function notificationCreator (user: MUserWithNotificationSetting) { |
254 | const notification = await UserNotificationModel.create({ | 268 | const notification = await UserNotificationModel.create<UserNotificationModelForApi>({ |
255 | type: UserNotificationType.NEW_FOLLOW, | 269 | type: UserNotificationType.NEW_FOLLOW, |
256 | userId: user.id, | 270 | userId: user.id, |
257 | actorFollowId: actorFollow.id | 271 | actorFollowId: actorFollow.id |
@@ -268,17 +282,17 @@ class Notifier { | |||
268 | return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender }) | 282 | return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender }) |
269 | } | 283 | } |
270 | 284 | ||
271 | private async notifyAdminsOfNewInstanceFollow (actorFollow: ActorFollowModel) { | 285 | private async notifyAdminsOfNewInstanceFollow (actorFollow: MActorFollowFull) { |
272 | const admins = await UserModel.listWithRight(UserRight.MANAGE_SERVER_FOLLOW) | 286 | const admins = await UserModel.listWithRight(UserRight.MANAGE_SERVER_FOLLOW) |
273 | 287 | ||
274 | logger.info('Notifying %d administrators of new instance follower: %s.', admins.length, actorFollow.ActorFollower.url) | 288 | logger.info('Notifying %d administrators of new instance follower: %s.', admins.length, actorFollow.ActorFollower.url) |
275 | 289 | ||
276 | function settingGetter (user: UserModel) { | 290 | function settingGetter (user: MUserWithNotificationSetting) { |
277 | return user.NotificationSetting.newInstanceFollower | 291 | return user.NotificationSetting.newInstanceFollower |
278 | } | 292 | } |
279 | 293 | ||
280 | async function notificationCreator (user: UserModel) { | 294 | async function notificationCreator (user: MUserWithNotificationSetting) { |
281 | const notification = await UserNotificationModel.create({ | 295 | const notification = await UserNotificationModel.create<UserNotificationModelForApi>({ |
282 | type: UserNotificationType.NEW_INSTANCE_FOLLOWER, | 296 | type: UserNotificationType.NEW_INSTANCE_FOLLOWER, |
283 | userId: user.id, | 297 | userId: user.id, |
284 | actorFollowId: actorFollow.id | 298 | actorFollowId: actorFollow.id |
@@ -295,18 +309,45 @@ class Notifier { | |||
295 | return this.notify({ users: admins, settingGetter, notificationCreator, emailSender }) | 309 | return this.notify({ users: admins, settingGetter, notificationCreator, emailSender }) |
296 | } | 310 | } |
297 | 311 | ||
298 | private async notifyModeratorsOfNewVideoAbuse (videoAbuse: VideoAbuseModel) { | 312 | private async notifyAdminsOfAutoInstanceFollowing (actorFollow: MActorFollowFull) { |
313 | const admins = await UserModel.listWithRight(UserRight.MANAGE_SERVER_FOLLOW) | ||
314 | |||
315 | logger.info('Notifying %d administrators of auto instance following: %s.', admins.length, actorFollow.ActorFollowing.url) | ||
316 | |||
317 | function settingGetter (user: MUserWithNotificationSetting) { | ||
318 | return user.NotificationSetting.autoInstanceFollowing | ||
319 | } | ||
320 | |||
321 | async function notificationCreator (user: MUserWithNotificationSetting) { | ||
322 | const notification = await UserNotificationModel.create<UserNotificationModelForApi>({ | ||
323 | type: UserNotificationType.AUTO_INSTANCE_FOLLOWING, | ||
324 | userId: user.id, | ||
325 | actorFollowId: actorFollow.id | ||
326 | }) | ||
327 | notification.ActorFollow = actorFollow | ||
328 | |||
329 | return notification | ||
330 | } | ||
331 | |||
332 | function emailSender (emails: string[]) { | ||
333 | return Emailer.Instance.addAutoInstanceFollowingNotification(emails, actorFollow) | ||
334 | } | ||
335 | |||
336 | return this.notify({ users: admins, settingGetter, notificationCreator, emailSender }) | ||
337 | } | ||
338 | |||
339 | private async notifyModeratorsOfNewVideoAbuse (videoAbuse: MVideoAbuseVideo) { | ||
299 | const moderators = await UserModel.listWithRight(UserRight.MANAGE_VIDEO_ABUSES) | 340 | const moderators = await UserModel.listWithRight(UserRight.MANAGE_VIDEO_ABUSES) |
300 | if (moderators.length === 0) return | 341 | if (moderators.length === 0) return |
301 | 342 | ||
302 | logger.info('Notifying %s user/moderators of new video abuse %s.', moderators.length, videoAbuse.Video.url) | 343 | logger.info('Notifying %s user/moderators of new video abuse %s.', moderators.length, videoAbuse.Video.url) |
303 | 344 | ||
304 | function settingGetter (user: UserModel) { | 345 | function settingGetter (user: MUserWithNotificationSetting) { |
305 | return user.NotificationSetting.videoAbuseAsModerator | 346 | return user.NotificationSetting.videoAbuseAsModerator |
306 | } | 347 | } |
307 | 348 | ||
308 | async function notificationCreator (user: UserModel) { | 349 | async function notificationCreator (user: MUserWithNotificationSetting) { |
309 | const notification = await UserNotificationModel.create({ | 350 | const notification: UserNotificationModelForApi = await UserNotificationModel.create<UserNotificationModelForApi>({ |
310 | type: UserNotificationType.NEW_VIDEO_ABUSE_FOR_MODERATORS, | 351 | type: UserNotificationType.NEW_VIDEO_ABUSE_FOR_MODERATORS, |
311 | userId: user.id, | 352 | userId: user.id, |
312 | videoAbuseId: videoAbuse.id | 353 | videoAbuseId: videoAbuse.id |
@@ -323,46 +364,46 @@ class Notifier { | |||
323 | return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender }) | 364 | return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender }) |
324 | } | 365 | } |
325 | 366 | ||
326 | private async notifyModeratorsOfVideoAutoBlacklist (video: VideoModel) { | 367 | private async notifyModeratorsOfVideoAutoBlacklist (videoBlacklist: MVideoBlacklistLightVideo) { |
327 | const moderators = await UserModel.listWithRight(UserRight.MANAGE_VIDEO_BLACKLIST) | 368 | const moderators = await UserModel.listWithRight(UserRight.MANAGE_VIDEO_BLACKLIST) |
328 | if (moderators.length === 0) return | 369 | if (moderators.length === 0) return |
329 | 370 | ||
330 | logger.info('Notifying %s moderators of video auto-blacklist %s.', moderators.length, video.url) | 371 | logger.info('Notifying %s moderators of video auto-blacklist %s.', moderators.length, videoBlacklist.Video.url) |
331 | 372 | ||
332 | function settingGetter (user: UserModel) { | 373 | function settingGetter (user: MUserWithNotificationSetting) { |
333 | return user.NotificationSetting.videoAutoBlacklistAsModerator | 374 | return user.NotificationSetting.videoAutoBlacklistAsModerator |
334 | } | 375 | } |
335 | async function notificationCreator (user: UserModel) { | ||
336 | 376 | ||
337 | const notification = await UserNotificationModel.create({ | 377 | async function notificationCreator (user: MUserWithNotificationSetting) { |
378 | const notification = await UserNotificationModel.create<UserNotificationModelForApi>({ | ||
338 | type: UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS, | 379 | type: UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS, |
339 | userId: user.id, | 380 | userId: user.id, |
340 | videoId: video.id | 381 | videoBlacklistId: videoBlacklist.id |
341 | }) | 382 | }) |
342 | notification.Video = video | 383 | notification.VideoBlacklist = videoBlacklist |
343 | 384 | ||
344 | return notification | 385 | return notification |
345 | } | 386 | } |
346 | 387 | ||
347 | function emailSender (emails: string[]) { | 388 | function emailSender (emails: string[]) { |
348 | return Emailer.Instance.addVideoAutoBlacklistModeratorsNotification(emails, video) | 389 | return Emailer.Instance.addVideoAutoBlacklistModeratorsNotification(emails, videoBlacklist) |
349 | } | 390 | } |
350 | 391 | ||
351 | return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender }) | 392 | return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender }) |
352 | } | 393 | } |
353 | 394 | ||
354 | private async notifyVideoOwnerOfBlacklist (videoBlacklist: VideoBlacklistModel) { | 395 | private async notifyVideoOwnerOfBlacklist (videoBlacklist: MVideoBlacklistVideo) { |
355 | const user = await UserModel.loadByVideoId(videoBlacklist.videoId) | 396 | const user = await UserModel.loadByVideoId(videoBlacklist.videoId) |
356 | if (!user) return | 397 | if (!user) return |
357 | 398 | ||
358 | logger.info('Notifying user %s that its video %s has been blacklisted.', user.username, videoBlacklist.Video.url) | 399 | logger.info('Notifying user %s that its video %s has been blacklisted.', user.username, videoBlacklist.Video.url) |
359 | 400 | ||
360 | function settingGetter (user: UserModel) { | 401 | function settingGetter (user: MUserWithNotificationSetting) { |
361 | return user.NotificationSetting.blacklistOnMyVideo | 402 | return user.NotificationSetting.blacklistOnMyVideo |
362 | } | 403 | } |
363 | 404 | ||
364 | async function notificationCreator (user: UserModel) { | 405 | async function notificationCreator (user: MUserWithNotificationSetting) { |
365 | const notification = await UserNotificationModel.create({ | 406 | const notification = await UserNotificationModel.create<UserNotificationModelForApi>({ |
366 | type: UserNotificationType.BLACKLIST_ON_MY_VIDEO, | 407 | type: UserNotificationType.BLACKLIST_ON_MY_VIDEO, |
367 | userId: user.id, | 408 | userId: user.id, |
368 | videoBlacklistId: videoBlacklist.id | 409 | videoBlacklistId: videoBlacklist.id |
@@ -379,18 +420,18 @@ class Notifier { | |||
379 | return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender }) | 420 | return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender }) |
380 | } | 421 | } |
381 | 422 | ||
382 | private async notifyVideoOwnerOfUnblacklist (video: VideoModel) { | 423 | private async notifyVideoOwnerOfUnblacklist (video: MVideoFullLight) { |
383 | const user = await UserModel.loadByVideoId(video.id) | 424 | const user = await UserModel.loadByVideoId(video.id) |
384 | if (!user) return | 425 | if (!user) return |
385 | 426 | ||
386 | logger.info('Notifying user %s that its video %s has been unblacklisted.', user.username, video.url) | 427 | logger.info('Notifying user %s that its video %s has been unblacklisted.', user.username, video.url) |
387 | 428 | ||
388 | function settingGetter (user: UserModel) { | 429 | function settingGetter (user: MUserWithNotificationSetting) { |
389 | return user.NotificationSetting.blacklistOnMyVideo | 430 | return user.NotificationSetting.blacklistOnMyVideo |
390 | } | 431 | } |
391 | 432 | ||
392 | async function notificationCreator (user: UserModel) { | 433 | async function notificationCreator (user: MUserWithNotificationSetting) { |
393 | const notification = await UserNotificationModel.create({ | 434 | const notification = await UserNotificationModel.create<UserNotificationModelForApi>({ |
394 | type: UserNotificationType.UNBLACKLIST_ON_MY_VIDEO, | 435 | type: UserNotificationType.UNBLACKLIST_ON_MY_VIDEO, |
395 | userId: user.id, | 436 | userId: user.id, |
396 | videoId: video.id | 437 | videoId: video.id |
@@ -407,18 +448,18 @@ class Notifier { | |||
407 | return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender }) | 448 | return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender }) |
408 | } | 449 | } |
409 | 450 | ||
410 | private async notifyOwnedVideoHasBeenPublished (video: VideoModel) { | 451 | private async notifyOwnedVideoHasBeenPublished (video: MVideoFullLight) { |
411 | const user = await UserModel.loadByVideoId(video.id) | 452 | const user = await UserModel.loadByVideoId(video.id) |
412 | if (!user) return | 453 | if (!user) return |
413 | 454 | ||
414 | logger.info('Notifying user %s of the publication of its video %s.', user.username, video.url) | 455 | logger.info('Notifying user %s of the publication of its video %s.', user.username, video.url) |
415 | 456 | ||
416 | function settingGetter (user: UserModel) { | 457 | function settingGetter (user: MUserWithNotificationSetting) { |
417 | return user.NotificationSetting.myVideoPublished | 458 | return user.NotificationSetting.myVideoPublished |
418 | } | 459 | } |
419 | 460 | ||
420 | async function notificationCreator (user: UserModel) { | 461 | async function notificationCreator (user: MUserWithNotificationSetting) { |
421 | const notification = await UserNotificationModel.create({ | 462 | const notification = await UserNotificationModel.create<UserNotificationModelForApi>({ |
422 | type: UserNotificationType.MY_VIDEO_PUBLISHED, | 463 | type: UserNotificationType.MY_VIDEO_PUBLISHED, |
423 | userId: user.id, | 464 | userId: user.id, |
424 | videoId: video.id | 465 | videoId: video.id |
@@ -435,18 +476,18 @@ class Notifier { | |||
435 | return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender }) | 476 | return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender }) |
436 | } | 477 | } |
437 | 478 | ||
438 | private async notifyOwnerVideoImportIsFinished (videoImport: VideoImportModel, success: boolean) { | 479 | private async notifyOwnerVideoImportIsFinished (videoImport: MVideoImportVideo, success: boolean) { |
439 | const user = await UserModel.loadByVideoImportId(videoImport.id) | 480 | const user = await UserModel.loadByVideoImportId(videoImport.id) |
440 | if (!user) return | 481 | if (!user) return |
441 | 482 | ||
442 | logger.info('Notifying user %s its video import %s is finished.', user.username, videoImport.getTargetIdentifier()) | 483 | logger.info('Notifying user %s its video import %s is finished.', user.username, videoImport.getTargetIdentifier()) |
443 | 484 | ||
444 | function settingGetter (user: UserModel) { | 485 | function settingGetter (user: MUserWithNotificationSetting) { |
445 | return user.NotificationSetting.myVideoImportFinished | 486 | return user.NotificationSetting.myVideoImportFinished |
446 | } | 487 | } |
447 | 488 | ||
448 | async function notificationCreator (user: UserModel) { | 489 | async function notificationCreator (user: MUserWithNotificationSetting) { |
449 | const notification = await UserNotificationModel.create({ | 490 | const notification = await UserNotificationModel.create<UserNotificationModelForApi>({ |
450 | type: success ? UserNotificationType.MY_VIDEO_IMPORT_SUCCESS : UserNotificationType.MY_VIDEO_IMPORT_ERROR, | 491 | type: success ? UserNotificationType.MY_VIDEO_IMPORT_SUCCESS : UserNotificationType.MY_VIDEO_IMPORT_ERROR, |
451 | userId: user.id, | 492 | userId: user.id, |
452 | videoImportId: videoImport.id | 493 | videoImportId: videoImport.id |
@@ -465,21 +506,21 @@ class Notifier { | |||
465 | return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender }) | 506 | return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender }) |
466 | } | 507 | } |
467 | 508 | ||
468 | private async notifyModeratorsOfNewUserRegistration (registeredUser: UserModel) { | 509 | private async notifyModeratorsOfNewUserRegistration (registeredUser: MUserDefault) { |
469 | const moderators = await UserModel.listWithRight(UserRight.MANAGE_USERS) | 510 | const moderators = await UserModel.listWithRight(UserRight.MANAGE_USERS) |
470 | if (moderators.length === 0) return | 511 | if (moderators.length === 0) return |
471 | 512 | ||
472 | logger.info( | 513 | logger.info( |
473 | 'Notifying %s moderators of new user registration of %s.', | 514 | 'Notifying %s moderators of new user registration of %s.', |
474 | moderators.length, registeredUser.Account.Actor.preferredUsername | 515 | moderators.length, registeredUser.username |
475 | ) | 516 | ) |
476 | 517 | ||
477 | function settingGetter (user: UserModel) { | 518 | function settingGetter (user: MUserWithNotificationSetting) { |
478 | return user.NotificationSetting.newUserRegistration | 519 | return user.NotificationSetting.newUserRegistration |
479 | } | 520 | } |
480 | 521 | ||
481 | async function notificationCreator (user: UserModel) { | 522 | async function notificationCreator (user: MUserWithNotificationSetting) { |
482 | const notification = await UserNotificationModel.create({ | 523 | const notification = await UserNotificationModel.create<UserNotificationModelForApi>({ |
483 | type: UserNotificationType.NEW_USER_REGISTRATION, | 524 | type: UserNotificationType.NEW_USER_REGISTRATION, |
484 | userId: user.id, | 525 | userId: user.id, |
485 | accountId: registeredUser.Account.id | 526 | accountId: registeredUser.Account.id |
@@ -496,11 +537,11 @@ class Notifier { | |||
496 | return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender }) | 537 | return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender }) |
497 | } | 538 | } |
498 | 539 | ||
499 | private async notify (options: { | 540 | private async notify <T extends MUserWithNotificationSetting> (options: { |
500 | users: UserModel[], | 541 | users: T[], |
501 | notificationCreator: (user: UserModel) => Promise<UserNotificationModel>, | 542 | notificationCreator: (user: T) => Promise<UserNotificationModelForApi>, |
502 | emailSender: (emails: string[]) => Promise<any> | Bluebird<any>, | 543 | emailSender: (emails: string[]) => Promise<any> | Bluebird<any>, |
503 | settingGetter: (user: UserModel) => UserNotificationSettingValue | 544 | settingGetter: (user: T) => UserNotificationSettingValue |
504 | }) { | 545 | }) { |
505 | const emails: string[] = [] | 546 | const emails: string[] = [] |
506 | 547 | ||
@@ -521,7 +562,7 @@ class Notifier { | |||
521 | } | 562 | } |
522 | } | 563 | } |
523 | 564 | ||
524 | private isEmailEnabled (user: UserModel, value: UserNotificationSettingValue) { | 565 | private isEmailEnabled (user: MUser, value: UserNotificationSettingValue) { |
525 | if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION === true && user.emailVerified === false) return false | 566 | if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION === true && user.emailVerified === false) return false |
526 | 567 | ||
527 | return value & UserNotificationSettingValue.EMAIL | 568 | return value & UserNotificationSettingValue.EMAIL |
diff --git a/server/lib/oauth-model.ts b/server/lib/oauth-model.ts index a1153e88a..086856f41 100644 --- a/server/lib/oauth-model.ts +++ b/server/lib/oauth-model.ts | |||
@@ -8,10 +8,11 @@ import { LRU_CACHE } from '../initializers/constants' | |||
8 | import { Transaction } from 'sequelize' | 8 | import { Transaction } from 'sequelize' |
9 | import { CONFIG } from '../initializers/config' | 9 | import { CONFIG } from '../initializers/config' |
10 | import * as LRUCache from 'lru-cache' | 10 | import * as LRUCache from 'lru-cache' |
11 | import { MOAuthTokenUser } from '@server/typings/models/oauth/oauth-token' | ||
11 | 12 | ||
12 | type TokenInfo = { accessToken: string, refreshToken: string, accessTokenExpiresAt: Date, refreshTokenExpiresAt: Date } | 13 | type TokenInfo = { accessToken: string, refreshToken: string, accessTokenExpiresAt: Date, refreshTokenExpiresAt: Date } |
13 | 14 | ||
14 | const accessTokenCache = new LRUCache<string, OAuthTokenModel>({ max: LRU_CACHE.USER_TOKENS.MAX_SIZE }) | 15 | const accessTokenCache = new LRUCache<string, MOAuthTokenUser>({ max: LRU_CACHE.USER_TOKENS.MAX_SIZE }) |
15 | const userHavingToken = new LRUCache<number, string>({ max: LRU_CACHE.USER_TOKENS.MAX_SIZE }) | 16 | const userHavingToken = new LRUCache<number, string>({ max: LRU_CACHE.USER_TOKENS.MAX_SIZE }) |
16 | 17 | ||
17 | // --------------------------------------------------------------------------- | 18 | // --------------------------------------------------------------------------- |
diff --git a/server/lib/peertube-socket.ts b/server/lib/peertube-socket.ts index 17748fd18..26ced351f 100644 --- a/server/lib/peertube-socket.ts +++ b/server/lib/peertube-socket.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | import * as SocketIO from 'socket.io' | 1 | import * as SocketIO from 'socket.io' |
2 | import { authenticateSocket } from '../middlewares' | 2 | import { authenticateSocket } from '../middlewares' |
3 | import { UserNotificationModel } from '../models/account/user-notification' | ||
4 | import { logger } from '../helpers/logger' | 3 | import { logger } from '../helpers/logger' |
5 | import { Server } from 'http' | 4 | import { Server } from 'http' |
5 | import { UserNotificationModelForApi } from '@server/typings/models/user' | ||
6 | 6 | ||
7 | class PeerTubeSocket { | 7 | class PeerTubeSocket { |
8 | 8 | ||
@@ -34,13 +34,14 @@ class PeerTubeSocket { | |||
34 | }) | 34 | }) |
35 | } | 35 | } |
36 | 36 | ||
37 | sendNotification (userId: number, notification: UserNotificationModel) { | 37 | sendNotification (userId: number, notification: UserNotificationModelForApi) { |
38 | const sockets = this.userNotificationSockets[userId] | 38 | const sockets = this.userNotificationSockets[userId] |
39 | 39 | ||
40 | if (!sockets) return | 40 | if (!sockets) return |
41 | 41 | ||
42 | const notificationMessage = notification.toFormattedJSON() | ||
42 | for (const socket of sockets) { | 43 | for (const socket of sockets) { |
43 | socket.emit('new-notification', notification.toFormattedJSON()) | 44 | socket.emit('new-notification', notificationMessage) |
44 | } | 45 | } |
45 | } | 46 | } |
46 | 47 | ||
diff --git a/server/lib/plugins/plugin-manager.ts b/server/lib/plugins/plugin-manager.ts index 444162a03..8127992b5 100644 --- a/server/lib/plugins/plugin-manager.ts +++ b/server/lib/plugins/plugin-manager.ts | |||
@@ -222,9 +222,8 @@ export class PluginManager implements ServerHook { | |||
222 | const pluginName = PluginModel.normalizePluginName(npmName) | 222 | const pluginName = PluginModel.normalizePluginName(npmName) |
223 | 223 | ||
224 | const packageJSON = await this.getPackageJSON(pluginName, pluginType) | 224 | const packageJSON = await this.getPackageJSON(pluginName, pluginType) |
225 | if (!isPackageJSONValid(packageJSON, pluginType)) { | 225 | |
226 | throw new Error('PackageJSON is invalid.') | 226 | this.sanitizeAndCheckPackageJSONOrThrow(packageJSON, pluginType); |
227 | } | ||
228 | 227 | ||
229 | [ plugin ] = await PluginModel.upsert({ | 228 | [ plugin ] = await PluginModel.upsert({ |
230 | name: pluginName, | 229 | name: pluginName, |
@@ -301,9 +300,7 @@ export class PluginManager implements ServerHook { | |||
301 | const packageJSON = await this.getPackageJSON(plugin.name, plugin.type) | 300 | const packageJSON = await this.getPackageJSON(plugin.name, plugin.type) |
302 | const pluginPath = this.getPluginPath(plugin.name, plugin.type) | 301 | const pluginPath = this.getPluginPath(plugin.name, plugin.type) |
303 | 302 | ||
304 | if (!isPackageJSONValid(packageJSON, plugin.type)) { | 303 | this.sanitizeAndCheckPackageJSONOrThrow(packageJSON, plugin.type) |
305 | throw new Error('Package.JSON is invalid.') | ||
306 | } | ||
307 | 304 | ||
308 | let library: PluginLibrary | 305 | let library: PluginLibrary |
309 | if (plugin.type === PluginType.PLUGIN) { | 306 | if (plugin.type === PluginType.PLUGIN) { |
@@ -598,6 +595,21 @@ export class PluginManager implements ServerHook { | |||
598 | } | 595 | } |
599 | } | 596 | } |
600 | 597 | ||
598 | private sanitizeAndCheckPackageJSONOrThrow (packageJSON: PluginPackageJson, pluginType: PluginType) { | ||
599 | if (!packageJSON.staticDirs) packageJSON.staticDirs = {} | ||
600 | if (!packageJSON.css) packageJSON.css = [] | ||
601 | if (!packageJSON.clientScripts) packageJSON.clientScripts = [] | ||
602 | if (!packageJSON.translations) packageJSON.translations = {} | ||
603 | |||
604 | const { result: packageJSONValid, badFields } = isPackageJSONValid(packageJSON, pluginType) | ||
605 | if (!packageJSONValid) { | ||
606 | const formattedFields = badFields.map(f => `"${f}"`) | ||
607 | .join(', ') | ||
608 | |||
609 | throw new Error(`PackageJSON is invalid (invalid fields: ${formattedFields}).`) | ||
610 | } | ||
611 | } | ||
612 | |||
601 | static get Instance () { | 613 | static get Instance () { |
602 | return this.instance || (this.instance = new this()) | 614 | return this.instance || (this.instance = new this()) |
603 | } | 615 | } |
diff --git a/server/lib/redundancy.ts b/server/lib/redundancy.ts index 04d3ded8f..1b4ecd7c0 100644 --- a/server/lib/redundancy.ts +++ b/server/lib/redundancy.ts | |||
@@ -2,8 +2,9 @@ import { VideoRedundancyModel } from '../models/redundancy/video-redundancy' | |||
2 | import { sendUndoCacheFile } from './activitypub/send' | 2 | import { sendUndoCacheFile } from './activitypub/send' |
3 | import { Transaction } from 'sequelize' | 3 | import { Transaction } from 'sequelize' |
4 | import { getServerActor } from '../helpers/utils' | 4 | import { getServerActor } from '../helpers/utils' |
5 | import { MVideoRedundancyVideo } from '@server/typings/models' | ||
5 | 6 | ||
6 | async function removeVideoRedundancy (videoRedundancy: VideoRedundancyModel, t?: Transaction) { | 7 | async function removeVideoRedundancy (videoRedundancy: MVideoRedundancyVideo, t?: Transaction) { |
7 | const serverActor = await getServerActor() | 8 | const serverActor = await getServerActor() |
8 | 9 | ||
9 | // Local cache, send undo to remote instances | 10 | // Local cache, send undo to remote instances |
diff --git a/server/lib/schedulers/auto-follow-index-instances.ts b/server/lib/schedulers/auto-follow-index-instances.ts new file mode 100644 index 000000000..ef11fc87f --- /dev/null +++ b/server/lib/schedulers/auto-follow-index-instances.ts | |||
@@ -0,0 +1,72 @@ | |||
1 | import { logger } from '../../helpers/logger' | ||
2 | import { AbstractScheduler } from './abstract-scheduler' | ||
3 | import { INSTANCES_INDEX, SCHEDULER_INTERVALS_MS, SERVER_ACTOR_NAME } from '../../initializers/constants' | ||
4 | import { CONFIG } from '../../initializers/config' | ||
5 | import { chunk } from 'lodash' | ||
6 | import { doRequest } from '@server/helpers/requests' | ||
7 | import { ActorFollowModel } from '@server/models/activitypub/actor-follow' | ||
8 | import { JobQueue } from '@server/lib/job-queue' | ||
9 | import { getServerActor } from '@server/helpers/utils' | ||
10 | |||
11 | export class AutoFollowIndexInstances extends AbstractScheduler { | ||
12 | |||
13 | private static instance: AbstractScheduler | ||
14 | |||
15 | protected schedulerIntervalMs = SCHEDULER_INTERVALS_MS.autoFollowIndexInstances | ||
16 | |||
17 | private lastCheck: Date | ||
18 | |||
19 | private constructor () { | ||
20 | super() | ||
21 | } | ||
22 | |||
23 | protected async internalExecute () { | ||
24 | return this.autoFollow() | ||
25 | } | ||
26 | |||
27 | private async autoFollow () { | ||
28 | if (CONFIG.FOLLOWINGS.INSTANCE.AUTO_FOLLOW_INDEX.ENABLED === false) return | ||
29 | |||
30 | const indexUrl = CONFIG.FOLLOWINGS.INSTANCE.AUTO_FOLLOW_INDEX.INDEX_URL | ||
31 | |||
32 | logger.info('Auto follow instances of index %s.', indexUrl) | ||
33 | |||
34 | try { | ||
35 | const serverActor = await getServerActor() | ||
36 | |||
37 | const uri = indexUrl + INSTANCES_INDEX.HOSTS_PATH | ||
38 | |||
39 | const qs = this.lastCheck ? { since: this.lastCheck.toISOString() } : {} | ||
40 | this.lastCheck = new Date() | ||
41 | |||
42 | const { body } = await doRequest({ uri, qs, json: true }) | ||
43 | |||
44 | const hosts: string[] = body.data.map(o => o.host) | ||
45 | const chunks = chunk(hosts, 20) | ||
46 | |||
47 | for (const chunk of chunks) { | ||
48 | const unfollowedHosts = await ActorFollowModel.keepUnfollowedInstance(chunk) | ||
49 | |||
50 | for (const unfollowedHost of unfollowedHosts) { | ||
51 | const payload = { | ||
52 | host: unfollowedHost, | ||
53 | name: SERVER_ACTOR_NAME, | ||
54 | followerActorId: serverActor.id, | ||
55 | isAutoFollow: true | ||
56 | } | ||
57 | |||
58 | await JobQueue.Instance.createJob({ type: 'activitypub-follow', payload }) | ||
59 | .catch(err => logger.error('Cannot create follow job for %s.', unfollowedHost, err)) | ||
60 | } | ||
61 | } | ||
62 | |||
63 | } catch (err) { | ||
64 | logger.error('Cannot auto follow hosts of index %s.', indexUrl, { err }) | ||
65 | } | ||
66 | |||
67 | } | ||
68 | |||
69 | static get Instance () { | ||
70 | return this.instance || (this.instance = new this()) | ||
71 | } | ||
72 | } | ||
diff --git a/server/lib/schedulers/videos-redundancy-scheduler.ts b/server/lib/schedulers/videos-redundancy-scheduler.ts index 5f4aad66e..1e30f6ebc 100644 --- a/server/lib/schedulers/videos-redundancy-scheduler.ts +++ b/server/lib/schedulers/videos-redundancy-scheduler.ts | |||
@@ -3,7 +3,6 @@ import { HLS_REDUNDANCY_DIRECTORY, REDUNDANCY, VIDEO_IMPORT_TIMEOUT, WEBSERVER } | |||
3 | import { logger } from '../../helpers/logger' | 3 | import { logger } from '../../helpers/logger' |
4 | import { VideosRedundancy } from '../../../shared/models/redundancy' | 4 | import { VideosRedundancy } from '../../../shared/models/redundancy' |
5 | import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy' | 5 | import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy' |
6 | import { VideoFileModel } from '../../models/video/video-file' | ||
7 | import { downloadWebTorrentVideo } from '../../helpers/webtorrent' | 6 | import { downloadWebTorrentVideo } from '../../helpers/webtorrent' |
8 | import { join } from 'path' | 7 | import { join } from 'path' |
9 | import { move } from 'fs-extra' | 8 | import { move } from 'fs-extra' |
@@ -12,16 +11,31 @@ import { sendCreateCacheFile, sendUpdateCacheFile } from '../activitypub/send' | |||
12 | import { getVideoCacheFileActivityPubUrl, getVideoCacheStreamingPlaylistActivityPubUrl } from '../activitypub/url' | 11 | import { getVideoCacheFileActivityPubUrl, getVideoCacheStreamingPlaylistActivityPubUrl } from '../activitypub/url' |
13 | import { removeVideoRedundancy } from '../redundancy' | 12 | import { removeVideoRedundancy } from '../redundancy' |
14 | import { getOrCreateVideoAndAccountAndChannel } from '../activitypub' | 13 | import { getOrCreateVideoAndAccountAndChannel } from '../activitypub' |
15 | import { VideoStreamingPlaylistModel } from '../../models/video/video-streaming-playlist' | ||
16 | import { VideoModel } from '../../models/video/video' | ||
17 | import { downloadPlaylistSegments } from '../hls' | 14 | import { downloadPlaylistSegments } from '../hls' |
18 | import { CONFIG } from '../../initializers/config' | 15 | import { CONFIG } from '../../initializers/config' |
16 | import { | ||
17 | MStreamingPlaylist, | ||
18 | MStreamingPlaylistVideo, | ||
19 | MVideoAccountLight, | ||
20 | MVideoFile, | ||
21 | MVideoFileVideo, | ||
22 | MVideoRedundancyFileVideo, | ||
23 | MVideoRedundancyStreamingPlaylistVideo, | ||
24 | MVideoRedundancyVideo, | ||
25 | MVideoWithAllFiles | ||
26 | } from '@server/typings/models' | ||
19 | 27 | ||
20 | type CandidateToDuplicate = { | 28 | type CandidateToDuplicate = { |
21 | redundancy: VideosRedundancy, | 29 | redundancy: VideosRedundancy, |
22 | video: VideoModel, | 30 | video: MVideoWithAllFiles, |
23 | files: VideoFileModel[], | 31 | files: MVideoFile[], |
24 | streamingPlaylists: VideoStreamingPlaylistModel[] | 32 | streamingPlaylists: MStreamingPlaylist[] |
33 | } | ||
34 | |||
35 | function isMVideoRedundancyFileVideo ( | ||
36 | o: MVideoRedundancyFileVideo | MVideoRedundancyStreamingPlaylistVideo | ||
37 | ): o is MVideoRedundancyFileVideo { | ||
38 | return !!(o as MVideoRedundancyFileVideo).VideoFile | ||
25 | } | 39 | } |
26 | 40 | ||
27 | export class VideosRedundancyScheduler extends AbstractScheduler { | 41 | export class VideosRedundancyScheduler extends AbstractScheduler { |
@@ -102,7 +116,7 @@ export class VideosRedundancyScheduler extends AbstractScheduler { | |||
102 | } | 116 | } |
103 | } | 117 | } |
104 | 118 | ||
105 | private async extendsRedundancy (redundancyModel: VideoRedundancyModel) { | 119 | private async extendsRedundancy (redundancyModel: MVideoRedundancyVideo) { |
106 | const redundancy = CONFIG.REDUNDANCY.VIDEOS.STRATEGIES.find(s => s.strategy === redundancyModel.strategy) | 120 | const redundancy = CONFIG.REDUNDANCY.VIDEOS.STRATEGIES.find(s => s.strategy === redundancyModel.strategy) |
107 | // Redundancy strategy disabled, remove our redundancy instead of extending expiration | 121 | // Redundancy strategy disabled, remove our redundancy instead of extending expiration |
108 | if (!redundancy) { | 122 | if (!redundancy) { |
@@ -172,7 +186,8 @@ export class VideosRedundancyScheduler extends AbstractScheduler { | |||
172 | } | 186 | } |
173 | } | 187 | } |
174 | 188 | ||
175 | private async createVideoFileRedundancy (redundancy: VideosRedundancy, video: VideoModel, file: VideoFileModel) { | 189 | private async createVideoFileRedundancy (redundancy: VideosRedundancy, video: MVideoAccountLight, fileArg: MVideoFile) { |
190 | const file = fileArg as MVideoFileVideo | ||
176 | file.Video = video | 191 | file.Video = video |
177 | 192 | ||
178 | const serverActor = await getServerActor() | 193 | const serverActor = await getServerActor() |
@@ -187,7 +202,7 @@ export class VideosRedundancyScheduler extends AbstractScheduler { | |||
187 | const destPath = join(CONFIG.STORAGE.REDUNDANCY_DIR, video.getVideoFilename(file)) | 202 | const destPath = join(CONFIG.STORAGE.REDUNDANCY_DIR, video.getVideoFilename(file)) |
188 | await move(tmpPath, destPath, { overwrite: true }) | 203 | await move(tmpPath, destPath, { overwrite: true }) |
189 | 204 | ||
190 | const createdModel = await VideoRedundancyModel.create({ | 205 | const createdModel: MVideoRedundancyFileVideo = await VideoRedundancyModel.create({ |
191 | expiresOn: this.buildNewExpiration(redundancy.minLifetime), | 206 | expiresOn: this.buildNewExpiration(redundancy.minLifetime), |
192 | url: getVideoCacheFileActivityPubUrl(file), | 207 | url: getVideoCacheFileActivityPubUrl(file), |
193 | fileUrl: video.getVideoRedundancyUrl(file, WEBSERVER.URL), | 208 | fileUrl: video.getVideoRedundancyUrl(file, WEBSERVER.URL), |
@@ -203,7 +218,12 @@ export class VideosRedundancyScheduler extends AbstractScheduler { | |||
203 | logger.info('Duplicated %s - %d -> %s.', video.url, file.resolution, createdModel.url) | 218 | logger.info('Duplicated %s - %d -> %s.', video.url, file.resolution, createdModel.url) |
204 | } | 219 | } |
205 | 220 | ||
206 | private async createStreamingPlaylistRedundancy (redundancy: VideosRedundancy, video: VideoModel, playlist: VideoStreamingPlaylistModel) { | 221 | private async createStreamingPlaylistRedundancy ( |
222 | redundancy: VideosRedundancy, | ||
223 | video: MVideoAccountLight, | ||
224 | playlistArg: MStreamingPlaylist | ||
225 | ) { | ||
226 | const playlist = playlistArg as MStreamingPlaylistVideo | ||
207 | playlist.Video = video | 227 | playlist.Video = video |
208 | 228 | ||
209 | const serverActor = await getServerActor() | 229 | const serverActor = await getServerActor() |
@@ -213,7 +233,7 @@ export class VideosRedundancyScheduler extends AbstractScheduler { | |||
213 | const destDirectory = join(HLS_REDUNDANCY_DIRECTORY, video.uuid) | 233 | const destDirectory = join(HLS_REDUNDANCY_DIRECTORY, video.uuid) |
214 | await downloadPlaylistSegments(playlist.playlistUrl, destDirectory, VIDEO_IMPORT_TIMEOUT) | 234 | await downloadPlaylistSegments(playlist.playlistUrl, destDirectory, VIDEO_IMPORT_TIMEOUT) |
215 | 235 | ||
216 | const createdModel = await VideoRedundancyModel.create({ | 236 | const createdModel: MVideoRedundancyStreamingPlaylistVideo = await VideoRedundancyModel.create({ |
217 | expiresOn: this.buildNewExpiration(redundancy.minLifetime), | 237 | expiresOn: this.buildNewExpiration(redundancy.minLifetime), |
218 | url: getVideoCacheStreamingPlaylistActivityPubUrl(video, playlist), | 238 | url: getVideoCacheStreamingPlaylistActivityPubUrl(video, playlist), |
219 | fileUrl: playlist.getVideoRedundancyUrl(WEBSERVER.URL), | 239 | fileUrl: playlist.getVideoRedundancyUrl(WEBSERVER.URL), |
@@ -229,7 +249,7 @@ export class VideosRedundancyScheduler extends AbstractScheduler { | |||
229 | logger.info('Duplicated playlist %s -> %s.', playlist.playlistUrl, createdModel.url) | 249 | logger.info('Duplicated playlist %s -> %s.', playlist.playlistUrl, createdModel.url) |
230 | } | 250 | } |
231 | 251 | ||
232 | private async extendsExpirationOf (redundancy: VideoRedundancyModel, expiresAfterMs: number) { | 252 | private async extendsExpirationOf (redundancy: MVideoRedundancyVideo, expiresAfterMs: number) { |
233 | logger.info('Extending expiration of %s.', redundancy.url) | 253 | logger.info('Extending expiration of %s.', redundancy.url) |
234 | 254 | ||
235 | const serverActor = await getServerActor() | 255 | const serverActor = await getServerActor() |
@@ -243,7 +263,7 @@ export class VideosRedundancyScheduler extends AbstractScheduler { | |||
243 | private async purgeCacheIfNeeded (candidateToDuplicate: CandidateToDuplicate) { | 263 | private async purgeCacheIfNeeded (candidateToDuplicate: CandidateToDuplicate) { |
244 | while (await this.isTooHeavy(candidateToDuplicate)) { | 264 | while (await this.isTooHeavy(candidateToDuplicate)) { |
245 | const redundancy = candidateToDuplicate.redundancy | 265 | const redundancy = candidateToDuplicate.redundancy |
246 | const toDelete = await VideoRedundancyModel.loadOldestLocalThatAlreadyExpired(redundancy.strategy, redundancy.minLifetime) | 266 | const toDelete = await VideoRedundancyModel.loadOldestLocalExpired(redundancy.strategy, redundancy.minLifetime) |
247 | if (!toDelete) return | 267 | if (!toDelete) return |
248 | 268 | ||
249 | await removeVideoRedundancy(toDelete) | 269 | await removeVideoRedundancy(toDelete) |
@@ -263,19 +283,18 @@ export class VideosRedundancyScheduler extends AbstractScheduler { | |||
263 | return new Date(Date.now() + expiresAfterMs) | 283 | return new Date(Date.now() + expiresAfterMs) |
264 | } | 284 | } |
265 | 285 | ||
266 | private buildEntryLogId (object: VideoRedundancyModel) { | 286 | private buildEntryLogId (object: MVideoRedundancyFileVideo | MVideoRedundancyStreamingPlaylistVideo) { |
267 | if (object.VideoFile) return `${object.VideoFile.Video.url}-${object.VideoFile.resolution}` | 287 | if (isMVideoRedundancyFileVideo(object)) return `${object.VideoFile.Video.url}-${object.VideoFile.resolution}` |
268 | 288 | ||
269 | return `${object.VideoStreamingPlaylist.playlistUrl}` | 289 | return `${object.VideoStreamingPlaylist.playlistUrl}` |
270 | } | 290 | } |
271 | 291 | ||
272 | private getTotalFileSizes (files: VideoFileModel[], playlists: VideoStreamingPlaylistModel[]) { | 292 | private getTotalFileSizes (files: MVideoFile[], playlists: MStreamingPlaylist[]) { |
273 | const fileReducer = (previous: number, current: VideoFileModel) => previous + current.size | 293 | const fileReducer = (previous: number, current: MVideoFile) => previous + current.size |
274 | 294 | ||
275 | const totalSize = files.reduce(fileReducer, 0) | 295 | const totalSize = files.reduce(fileReducer, 0) |
276 | if (playlists.length === 0) return totalSize | ||
277 | 296 | ||
278 | return totalSize * playlists.length | 297 | return totalSize + (totalSize * playlists.length) |
279 | } | 298 | } |
280 | 299 | ||
281 | private async loadAndRefreshVideo (videoUrl: string) { | 300 | private async loadAndRefreshVideo (videoUrl: string) { |
diff --git a/server/lib/thumbnail.ts b/server/lib/thumbnail.ts index a59773f5a..84791955e 100644 --- a/server/lib/thumbnail.ts +++ b/server/lib/thumbnail.ts | |||
@@ -1,20 +1,20 @@ | |||
1 | import { VideoFileModel } from '../models/video/video-file' | ||
2 | import { generateImageFromVideoFile } from '../helpers/ffmpeg-utils' | 1 | import { generateImageFromVideoFile } from '../helpers/ffmpeg-utils' |
3 | import { CONFIG } from '../initializers/config' | 2 | import { CONFIG } from '../initializers/config' |
4 | import { PREVIEWS_SIZE, THUMBNAILS_SIZE, ASSETS_PATH } from '../initializers/constants' | 3 | import { ASSETS_PATH, PREVIEWS_SIZE, THUMBNAILS_SIZE } from '../initializers/constants' |
5 | import { VideoModel } from '../models/video/video' | ||
6 | import { ThumbnailModel } from '../models/video/thumbnail' | 4 | import { ThumbnailModel } from '../models/video/thumbnail' |
7 | import { ThumbnailType } from '../../shared/models/videos/thumbnail.type' | 5 | import { ThumbnailType } from '../../shared/models/videos/thumbnail.type' |
8 | import { processImage } from '../helpers/image-utils' | 6 | import { processImage } from '../helpers/image-utils' |
9 | import { join } from 'path' | 7 | import { join } from 'path' |
10 | import { downloadImage } from '../helpers/requests' | 8 | import { downloadImage } from '../helpers/requests' |
11 | import { VideoPlaylistModel } from '../models/video/video-playlist' | 9 | import { MVideoPlaylistThumbnail } from '../typings/models/video/video-playlist' |
10 | import { MVideoFile, MVideoThumbnail } from '../typings/models' | ||
11 | import { MThumbnail } from '../typings/models/video/thumbnail' | ||
12 | 12 | ||
13 | type ImageSize = { height: number, width: number } | 13 | type ImageSize = { height: number, width: number } |
14 | 14 | ||
15 | function createPlaylistMiniatureFromExisting ( | 15 | function createPlaylistMiniatureFromExisting ( |
16 | inputPath: string, | 16 | inputPath: string, |
17 | playlist: VideoPlaylistModel, | 17 | playlist: MVideoPlaylistThumbnail, |
18 | automaticallyGenerated: boolean, | 18 | automaticallyGenerated: boolean, |
19 | keepOriginal = false, | 19 | keepOriginal = false, |
20 | size?: ImageSize | 20 | size?: ImageSize |
@@ -26,7 +26,7 @@ function createPlaylistMiniatureFromExisting ( | |||
26 | return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, automaticallyGenerated, existingThumbnail }) | 26 | return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, automaticallyGenerated, existingThumbnail }) |
27 | } | 27 | } |
28 | 28 | ||
29 | function createPlaylistMiniatureFromUrl (fileUrl: string, playlist: VideoPlaylistModel, size?: ImageSize) { | 29 | function createPlaylistMiniatureFromUrl (fileUrl: string, playlist: MVideoPlaylistThumbnail, size?: ImageSize) { |
30 | const { filename, basePath, height, width, existingThumbnail } = buildMetadataFromPlaylist(playlist, size) | 30 | const { filename, basePath, height, width, existingThumbnail } = buildMetadataFromPlaylist(playlist, size) |
31 | const type = ThumbnailType.MINIATURE | 31 | const type = ThumbnailType.MINIATURE |
32 | 32 | ||
@@ -34,7 +34,7 @@ function createPlaylistMiniatureFromUrl (fileUrl: string, playlist: VideoPlaylis | |||
34 | return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail, fileUrl }) | 34 | return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail, fileUrl }) |
35 | } | 35 | } |
36 | 36 | ||
37 | function createVideoMiniatureFromUrl (fileUrl: string, video: VideoModel, type: ThumbnailType, size?: ImageSize) { | 37 | function createVideoMiniatureFromUrl (fileUrl: string, video: MVideoThumbnail, type: ThumbnailType, size?: ImageSize) { |
38 | const { filename, basePath, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size) | 38 | const { filename, basePath, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size) |
39 | const thumbnailCreator = () => downloadImage(fileUrl, basePath, filename, { width, height }) | 39 | const thumbnailCreator = () => downloadImage(fileUrl, basePath, filename, { width, height }) |
40 | 40 | ||
@@ -43,7 +43,7 @@ function createVideoMiniatureFromUrl (fileUrl: string, video: VideoModel, type: | |||
43 | 43 | ||
44 | function createVideoMiniatureFromExisting ( | 44 | function createVideoMiniatureFromExisting ( |
45 | inputPath: string, | 45 | inputPath: string, |
46 | video: VideoModel, | 46 | video: MVideoThumbnail, |
47 | type: ThumbnailType, | 47 | type: ThumbnailType, |
48 | automaticallyGenerated: boolean, | 48 | automaticallyGenerated: boolean, |
49 | size?: ImageSize | 49 | size?: ImageSize |
@@ -54,7 +54,7 @@ function createVideoMiniatureFromExisting ( | |||
54 | return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, automaticallyGenerated, existingThumbnail }) | 54 | return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, automaticallyGenerated, existingThumbnail }) |
55 | } | 55 | } |
56 | 56 | ||
57 | function generateVideoMiniature (video: VideoModel, videoFile: VideoFileModel, type: ThumbnailType) { | 57 | function generateVideoMiniature (video: MVideoThumbnail, videoFile: MVideoFile, type: ThumbnailType) { |
58 | const input = video.getVideoFilePath(videoFile) | 58 | const input = video.getVideoFilePath(videoFile) |
59 | 59 | ||
60 | const { filename, basePath, height, width, existingThumbnail, outputPath } = buildMetadataFromVideo(video, type) | 60 | const { filename, basePath, height, width, existingThumbnail, outputPath } = buildMetadataFromVideo(video, type) |
@@ -65,7 +65,7 @@ function generateVideoMiniature (video: VideoModel, videoFile: VideoFileModel, t | |||
65 | return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, automaticallyGenerated: true, existingThumbnail }) | 65 | return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, automaticallyGenerated: true, existingThumbnail }) |
66 | } | 66 | } |
67 | 67 | ||
68 | function createPlaceholderThumbnail (fileUrl: string, video: VideoModel, type: ThumbnailType, size: ImageSize) { | 68 | function createPlaceholderThumbnail (fileUrl: string, video: MVideoThumbnail, type: ThumbnailType, size: ImageSize) { |
69 | const { filename, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size) | 69 | const { filename, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size) |
70 | 70 | ||
71 | const thumbnail = existingThumbnail ? existingThumbnail : new ThumbnailModel() | 71 | const thumbnail = existingThumbnail ? existingThumbnail : new ThumbnailModel() |
@@ -90,7 +90,7 @@ export { | |||
90 | createPlaylistMiniatureFromExisting | 90 | createPlaylistMiniatureFromExisting |
91 | } | 91 | } |
92 | 92 | ||
93 | function buildMetadataFromPlaylist (playlist: VideoPlaylistModel, size: ImageSize) { | 93 | function buildMetadataFromPlaylist (playlist: MVideoPlaylistThumbnail, size: ImageSize) { |
94 | const filename = playlist.generateThumbnailName() | 94 | const filename = playlist.generateThumbnailName() |
95 | const basePath = CONFIG.STORAGE.THUMBNAILS_DIR | 95 | const basePath = CONFIG.STORAGE.THUMBNAILS_DIR |
96 | 96 | ||
@@ -104,7 +104,7 @@ function buildMetadataFromPlaylist (playlist: VideoPlaylistModel, size: ImageSiz | |||
104 | } | 104 | } |
105 | } | 105 | } |
106 | 106 | ||
107 | function buildMetadataFromVideo (video: VideoModel, type: ThumbnailType, size?: ImageSize) { | 107 | function buildMetadataFromVideo (video: MVideoThumbnail, type: ThumbnailType, size?: ImageSize) { |
108 | const existingThumbnail = Array.isArray(video.Thumbnails) | 108 | const existingThumbnail = Array.isArray(video.Thumbnails) |
109 | ? video.Thumbnails.find(t => t.type === type) | 109 | ? video.Thumbnails.find(t => t.type === type) |
110 | : undefined | 110 | : undefined |
@@ -148,7 +148,7 @@ async function createThumbnailFromFunction (parameters: { | |||
148 | type: ThumbnailType, | 148 | type: ThumbnailType, |
149 | automaticallyGenerated?: boolean, | 149 | automaticallyGenerated?: boolean, |
150 | fileUrl?: string, | 150 | fileUrl?: string, |
151 | existingThumbnail?: ThumbnailModel | 151 | existingThumbnail?: MThumbnail |
152 | }) { | 152 | }) { |
153 | const { thumbnailCreator, filename, width, height, type, existingThumbnail, automaticallyGenerated = null, fileUrl = null } = parameters | 153 | const { thumbnailCreator, filename, width, height, type, existingThumbnail, automaticallyGenerated = null, fileUrl = null } = parameters |
154 | 154 | ||
diff --git a/server/lib/user.ts b/server/lib/user.ts index 0e4007770..c45438d95 100644 --- a/server/lib/user.ts +++ b/server/lib/user.ts | |||
@@ -2,10 +2,8 @@ import * as uuidv4 from 'uuid/v4' | |||
2 | import { ActivityPubActorType } from '../../shared/models/activitypub' | 2 | import { ActivityPubActorType } from '../../shared/models/activitypub' |
3 | import { SERVER_ACTOR_NAME, WEBSERVER } from '../initializers/constants' | 3 | import { SERVER_ACTOR_NAME, WEBSERVER } from '../initializers/constants' |
4 | import { AccountModel } from '../models/account/account' | 4 | import { AccountModel } from '../models/account/account' |
5 | import { UserModel } from '../models/account/user' | ||
6 | import { buildActorInstance, getAccountActivityPubUrl, setAsyncActorKeys } from './activitypub' | 5 | import { buildActorInstance, getAccountActivityPubUrl, setAsyncActorKeys } from './activitypub' |
7 | import { createVideoChannel } from './video-channel' | 6 | import { createLocalVideoChannel } from './video-channel' |
8 | import { VideoChannelModel } from '../models/video/video-channel' | ||
9 | import { ActorModel } from '../models/activitypub/actor' | 7 | import { ActorModel } from '../models/activitypub/actor' |
10 | import { UserNotificationSettingModel } from '../models/account/user-notification-setting' | 8 | import { UserNotificationSettingModel } from '../models/account/user-notification-setting' |
11 | import { UserNotificationSetting, UserNotificationSettingValue } from '../../shared/models/users' | 9 | import { UserNotificationSetting, UserNotificationSettingValue } from '../../shared/models/users' |
@@ -14,14 +12,17 @@ import { sequelizeTypescript } from '../initializers/database' | |||
14 | import { Transaction } from 'sequelize/types' | 12 | import { Transaction } from 'sequelize/types' |
15 | import { Redis } from './redis' | 13 | import { Redis } from './redis' |
16 | import { Emailer } from './emailer' | 14 | import { Emailer } from './emailer' |
15 | import { MAccountDefault, MActorDefault, MChannelActor } from '../typings/models' | ||
16 | import { MUser, MUserDefault, MUserId } from '../typings/models/user' | ||
17 | 17 | ||
18 | type ChannelNames = { name: string, displayName: string } | 18 | type ChannelNames = { name: string, displayName: string } |
19 | |||
19 | async function createUserAccountAndChannelAndPlaylist (parameters: { | 20 | async function createUserAccountAndChannelAndPlaylist (parameters: { |
20 | userToCreate: UserModel, | 21 | userToCreate: MUser, |
21 | userDisplayName?: string, | 22 | userDisplayName?: string, |
22 | channelNames?: ChannelNames, | 23 | channelNames?: ChannelNames, |
23 | validateUser?: boolean | 24 | validateUser?: boolean |
24 | }) { | 25 | }): Promise<{ user: MUserDefault, account: MAccountDefault, videoChannel: MChannelActor }> { |
25 | const { userToCreate, userDisplayName, channelNames, validateUser = true } = parameters | 26 | const { userToCreate, userDisplayName, channelNames, validateUser = true } = parameters |
26 | 27 | ||
27 | const { user, account, videoChannel } = await sequelizeTypescript.transaction(async t => { | 28 | const { user, account, videoChannel } = await sequelizeTypescript.transaction(async t => { |
@@ -30,7 +31,7 @@ async function createUserAccountAndChannelAndPlaylist (parameters: { | |||
30 | validate: validateUser | 31 | validate: validateUser |
31 | } | 32 | } |
32 | 33 | ||
33 | const userCreated = await userToCreate.save(userOptions) | 34 | const userCreated: MUserDefault = await userToCreate.save(userOptions) |
34 | userCreated.NotificationSetting = await createDefaultUserNotificationSettings(userCreated, t) | 35 | userCreated.NotificationSetting = await createDefaultUserNotificationSettings(userCreated, t) |
35 | 36 | ||
36 | const accountCreated = await createLocalAccountWithoutKeys({ | 37 | const accountCreated = await createLocalAccountWithoutKeys({ |
@@ -43,22 +44,22 @@ async function createUserAccountAndChannelAndPlaylist (parameters: { | |||
43 | userCreated.Account = accountCreated | 44 | userCreated.Account = accountCreated |
44 | 45 | ||
45 | const channelAttributes = await buildChannelAttributes(userCreated, channelNames) | 46 | const channelAttributes = await buildChannelAttributes(userCreated, channelNames) |
46 | const videoChannel = await createVideoChannel(channelAttributes, accountCreated, t) | 47 | const videoChannel = await createLocalVideoChannel(channelAttributes, accountCreated, t) |
47 | 48 | ||
48 | const videoPlaylist = await createWatchLaterPlaylist(accountCreated, t) | 49 | const videoPlaylist = await createWatchLaterPlaylist(accountCreated, t) |
49 | 50 | ||
50 | return { user: userCreated, account: accountCreated, videoChannel, videoPlaylist } | 51 | return { user: userCreated, account: accountCreated, videoChannel, videoPlaylist } |
51 | }) | 52 | }) |
52 | 53 | ||
53 | const [ accountKeys, channelKeys ] = await Promise.all([ | 54 | const [ accountActorWithKeys, channelActorWithKeys ] = await Promise.all([ |
54 | setAsyncActorKeys(account.Actor), | 55 | setAsyncActorKeys(account.Actor), |
55 | setAsyncActorKeys(videoChannel.Actor) | 56 | setAsyncActorKeys(videoChannel.Actor) |
56 | ]) | 57 | ]) |
57 | 58 | ||
58 | account.Actor = accountKeys | 59 | account.Actor = accountActorWithKeys |
59 | videoChannel.Actor = channelKeys | 60 | videoChannel.Actor = channelActorWithKeys |
60 | 61 | ||
61 | return { user, account, videoChannel } as { user: UserModel, account: AccountModel, videoChannel: VideoChannelModel } | 62 | return { user, account, videoChannel } |
62 | } | 63 | } |
63 | 64 | ||
64 | async function createLocalAccountWithoutKeys (parameters: { | 65 | async function createLocalAccountWithoutKeys (parameters: { |
@@ -73,7 +74,7 @@ async function createLocalAccountWithoutKeys (parameters: { | |||
73 | const url = getAccountActivityPubUrl(name) | 74 | const url = getAccountActivityPubUrl(name) |
74 | 75 | ||
75 | const actorInstance = buildActorInstance(type, url, name) | 76 | const actorInstance = buildActorInstance(type, url, name) |
76 | const actorInstanceCreated = await actorInstance.save({ transaction: t }) | 77 | const actorInstanceCreated: MActorDefault = await actorInstance.save({ transaction: t }) |
77 | 78 | ||
78 | const accountInstance = new AccountModel({ | 79 | const accountInstance = new AccountModel({ |
79 | name: displayName || name, | 80 | name: displayName || name, |
@@ -82,7 +83,7 @@ async function createLocalAccountWithoutKeys (parameters: { | |||
82 | actorId: actorInstanceCreated.id | 83 | actorId: actorInstanceCreated.id |
83 | }) | 84 | }) |
84 | 85 | ||
85 | const accountInstanceCreated = await accountInstance.save({ transaction: t }) | 86 | const accountInstanceCreated: MAccountDefault = await accountInstance.save({ transaction: t }) |
86 | accountInstanceCreated.Actor = actorInstanceCreated | 87 | accountInstanceCreated.Actor = actorInstanceCreated |
87 | 88 | ||
88 | return accountInstanceCreated | 89 | return accountInstanceCreated |
@@ -102,7 +103,7 @@ async function createApplicationActor (applicationId: number) { | |||
102 | return accountCreated | 103 | return accountCreated |
103 | } | 104 | } |
104 | 105 | ||
105 | async function sendVerifyUserEmail (user: UserModel, isPendingEmail = false) { | 106 | async function sendVerifyUserEmail (user: MUser, isPendingEmail = false) { |
106 | const verificationString = await Redis.Instance.setVerifyEmailVerificationString(user.id) | 107 | const verificationString = await Redis.Instance.setVerifyEmailVerificationString(user.id) |
107 | let url = WEBSERVER.URL + '/verify-account/email?userId=' + user.id + '&verificationString=' + verificationString | 108 | let url = WEBSERVER.URL + '/verify-account/email?userId=' + user.id + '&verificationString=' + verificationString |
108 | 109 | ||
@@ -124,7 +125,7 @@ export { | |||
124 | 125 | ||
125 | // --------------------------------------------------------------------------- | 126 | // --------------------------------------------------------------------------- |
126 | 127 | ||
127 | function createDefaultUserNotificationSettings (user: UserModel, t: Transaction | undefined) { | 128 | function createDefaultUserNotificationSettings (user: MUserId, t: Transaction | undefined) { |
128 | const values: UserNotificationSetting & { userId: number } = { | 129 | const values: UserNotificationSetting & { userId: number } = { |
129 | userId: user.id, | 130 | userId: user.id, |
130 | newVideoFromSubscription: UserNotificationSettingValue.WEB, | 131 | newVideoFromSubscription: UserNotificationSettingValue.WEB, |
@@ -137,13 +138,14 @@ function createDefaultUserNotificationSettings (user: UserModel, t: Transaction | |||
137 | newUserRegistration: UserNotificationSettingValue.WEB, | 138 | newUserRegistration: UserNotificationSettingValue.WEB, |
138 | commentMention: UserNotificationSettingValue.WEB, | 139 | commentMention: UserNotificationSettingValue.WEB, |
139 | newFollow: UserNotificationSettingValue.WEB, | 140 | newFollow: UserNotificationSettingValue.WEB, |
140 | newInstanceFollower: UserNotificationSettingValue.WEB | 141 | newInstanceFollower: UserNotificationSettingValue.WEB, |
142 | autoInstanceFollowing: UserNotificationSettingValue.WEB | ||
141 | } | 143 | } |
142 | 144 | ||
143 | return UserNotificationSettingModel.create(values, { transaction: t }) | 145 | return UserNotificationSettingModel.create(values, { transaction: t }) |
144 | } | 146 | } |
145 | 147 | ||
146 | async function buildChannelAttributes (user: UserModel, channelNames?: ChannelNames) { | 148 | async function buildChannelAttributes (user: MUser, channelNames?: ChannelNames) { |
147 | if (channelNames) return channelNames | 149 | if (channelNames) return channelNames |
148 | 150 | ||
149 | let channelName = user.username + '_channel' | 151 | let channelName = user.username + '_channel' |
diff --git a/server/lib/video-blacklist.ts b/server/lib/video-blacklist.ts index bdaecd8e2..1dd45b76d 100644 --- a/server/lib/video-blacklist.ts +++ b/server/lib/video-blacklist.ts | |||
@@ -2,16 +2,15 @@ import { Transaction } from 'sequelize' | |||
2 | import { CONFIG } from '../initializers/config' | 2 | import { CONFIG } from '../initializers/config' |
3 | import { UserRight, VideoBlacklistType } from '../../shared/models' | 3 | import { UserRight, VideoBlacklistType } from '../../shared/models' |
4 | import { VideoBlacklistModel } from '../models/video/video-blacklist' | 4 | import { VideoBlacklistModel } from '../models/video/video-blacklist' |
5 | import { UserModel } from '../models/account/user' | ||
6 | import { VideoModel } from '../models/video/video' | ||
7 | import { logger } from '../helpers/logger' | 5 | import { logger } from '../helpers/logger' |
8 | import { UserAdminFlag } from '../../shared/models/users/user-flag.model' | 6 | import { UserAdminFlag } from '../../shared/models/users/user-flag.model' |
9 | import { Hooks } from './plugins/hooks' | 7 | import { Hooks } from './plugins/hooks' |
10 | import { Notifier } from './notifier' | 8 | import { Notifier } from './notifier' |
9 | import { MUser, MVideoBlacklistVideo, MVideoWithBlacklistLight } from '@server/typings/models' | ||
11 | 10 | ||
12 | async function autoBlacklistVideoIfNeeded (parameters: { | 11 | async function autoBlacklistVideoIfNeeded (parameters: { |
13 | video: VideoModel, | 12 | video: MVideoWithBlacklistLight, |
14 | user?: UserModel, | 13 | user?: MUser, |
15 | isRemote: boolean, | 14 | isRemote: boolean, |
16 | isNew: boolean, | 15 | isNew: boolean, |
17 | notify?: boolean, | 16 | notify?: boolean, |
@@ -32,7 +31,7 @@ async function autoBlacklistVideoIfNeeded (parameters: { | |||
32 | reason: 'Auto-blacklisted. Moderator review required.', | 31 | reason: 'Auto-blacklisted. Moderator review required.', |
33 | type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED | 32 | type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED |
34 | } | 33 | } |
35 | const [ videoBlacklist ] = await VideoBlacklistModel.findOrCreate({ | 34 | const [ videoBlacklist ] = await VideoBlacklistModel.findOrCreate<MVideoBlacklistVideo>({ |
36 | where: { | 35 | where: { |
37 | videoId: video.id | 36 | videoId: video.id |
38 | }, | 37 | }, |
@@ -41,7 +40,9 @@ async function autoBlacklistVideoIfNeeded (parameters: { | |||
41 | }) | 40 | }) |
42 | video.VideoBlacklist = videoBlacklist | 41 | video.VideoBlacklist = videoBlacklist |
43 | 42 | ||
44 | if (notify) Notifier.Instance.notifyOnVideoAutoBlacklist(video) | 43 | videoBlacklist.Video = video |
44 | |||
45 | if (notify) Notifier.Instance.notifyOnVideoAutoBlacklist(videoBlacklist) | ||
45 | 46 | ||
46 | logger.info('Video %s auto-blacklisted.', video.uuid) | 47 | logger.info('Video %s auto-blacklisted.', video.uuid) |
47 | 48 | ||
@@ -49,10 +50,10 @@ async function autoBlacklistVideoIfNeeded (parameters: { | |||
49 | } | 50 | } |
50 | 51 | ||
51 | async function autoBlacklistNeeded (parameters: { | 52 | async function autoBlacklistNeeded (parameters: { |
52 | video: VideoModel, | 53 | video: MVideoWithBlacklistLight, |
53 | isRemote: boolean, | 54 | isRemote: boolean, |
54 | isNew: boolean, | 55 | isNew: boolean, |
55 | user?: UserModel | 56 | user?: MUser |
56 | }) { | 57 | }) { |
57 | const { user, video, isRemote, isNew } = parameters | 58 | const { user, video, isRemote, isNew } = parameters |
58 | 59 | ||
diff --git a/server/lib/video-channel.ts b/server/lib/video-channel.ts index ee0482c36..41eab456b 100644 --- a/server/lib/video-channel.ts +++ b/server/lib/video-channel.ts | |||
@@ -1,12 +1,19 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import * as uuidv4 from 'uuid/v4' | 2 | import * as uuidv4 from 'uuid/v4' |
3 | import { VideoChannelCreate } from '../../shared/models' | 3 | import { VideoChannelCreate } from '../../shared/models' |
4 | import { AccountModel } from '../models/account/account' | ||
5 | import { VideoChannelModel } from '../models/video/video-channel' | 4 | import { VideoChannelModel } from '../models/video/video-channel' |
6 | import { buildActorInstance, federateVideoIfNeeded, getVideoChannelActivityPubUrl } from './activitypub' | 5 | import { buildActorInstance, federateVideoIfNeeded, getVideoChannelActivityPubUrl } from './activitypub' |
7 | import { VideoModel } from '../models/video/video' | 6 | import { VideoModel } from '../models/video/video' |
7 | import { MAccountId, MChannelDefault, MChannelId } from '../typings/models' | ||
8 | 8 | ||
9 | async function createVideoChannel (videoChannelInfo: VideoChannelCreate, account: AccountModel, t: Sequelize.Transaction) { | 9 | type CustomVideoChannelModelAccount <T extends MAccountId> = MChannelDefault & |
10 | { Account?: T } | ||
11 | |||
12 | async function createLocalVideoChannel <T extends MAccountId> ( | ||
13 | videoChannelInfo: VideoChannelCreate, | ||
14 | account: T, | ||
15 | t: Sequelize.Transaction | ||
16 | ): Promise<CustomVideoChannelModelAccount<T>> { | ||
10 | const uuid = uuidv4() | 17 | const uuid = uuidv4() |
11 | const url = getVideoChannelActivityPubUrl(videoChannelInfo.name) | 18 | const url = getVideoChannelActivityPubUrl(videoChannelInfo.name) |
12 | const actorInstance = buildActorInstance('Group', url, videoChannelInfo.name, uuid) | 19 | const actorInstance = buildActorInstance('Group', url, videoChannelInfo.name, uuid) |
@@ -21,10 +28,10 @@ async function createVideoChannel (videoChannelInfo: VideoChannelCreate, account | |||
21 | actorId: actorInstanceCreated.id | 28 | actorId: actorInstanceCreated.id |
22 | } | 29 | } |
23 | 30 | ||
24 | const videoChannel = VideoChannelModel.build(videoChannelData) | 31 | const videoChannel = new VideoChannelModel(videoChannelData) |
25 | 32 | ||
26 | const options = { transaction: t } | 33 | const options = { transaction: t } |
27 | const videoChannelCreated = await videoChannel.save(options) | 34 | const videoChannelCreated: CustomVideoChannelModelAccount<T> = await videoChannel.save(options) as MChannelDefault |
28 | 35 | ||
29 | // Do not forget to add Account/Actor information to the created video channel | 36 | // Do not forget to add Account/Actor information to the created video channel |
30 | videoChannelCreated.Account = account | 37 | videoChannelCreated.Account = account |
@@ -34,7 +41,7 @@ async function createVideoChannel (videoChannelInfo: VideoChannelCreate, account | |||
34 | return videoChannelCreated | 41 | return videoChannelCreated |
35 | } | 42 | } |
36 | 43 | ||
37 | async function federateAllVideosOfChannel (videoChannel: VideoChannelModel) { | 44 | async function federateAllVideosOfChannel (videoChannel: MChannelId) { |
38 | const videoIds = await VideoModel.getAllIdsFromChannel(videoChannel) | 45 | const videoIds = await VideoModel.getAllIdsFromChannel(videoChannel) |
39 | 46 | ||
40 | for (const videoId of videoIds) { | 47 | for (const videoId of videoIds) { |
@@ -47,6 +54,6 @@ async function federateAllVideosOfChannel (videoChannel: VideoChannelModel) { | |||
47 | // --------------------------------------------------------------------------- | 54 | // --------------------------------------------------------------------------- |
48 | 55 | ||
49 | export { | 56 | export { |
50 | createVideoChannel, | 57 | createLocalVideoChannel, |
51 | federateAllVideosOfChannel | 58 | federateAllVideosOfChannel |
52 | } | 59 | } |
diff --git a/server/lib/video-comment.ts b/server/lib/video-comment.ts index 449aa74cb..bb811bd2c 100644 --- a/server/lib/video-comment.ts +++ b/server/lib/video-comment.ts | |||
@@ -1,17 +1,16 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import { ResultList } from '../../shared/models' | 2 | import { ResultList } from '../../shared/models' |
3 | import { VideoCommentThreadTree } from '../../shared/models/videos/video-comment.model' | 3 | import { VideoCommentThreadTree } from '../../shared/models/videos/video-comment.model' |
4 | import { AccountModel } from '../models/account/account' | ||
5 | import { VideoModel } from '../models/video/video' | ||
6 | import { VideoCommentModel } from '../models/video/video-comment' | 4 | import { VideoCommentModel } from '../models/video/video-comment' |
7 | import { getVideoCommentActivityPubUrl } from './activitypub' | 5 | import { getVideoCommentActivityPubUrl } from './activitypub' |
8 | import { sendCreateVideoComment } from './activitypub/send' | 6 | import { sendCreateVideoComment } from './activitypub/send' |
7 | import { MAccountDefault, MComment, MCommentOwnerVideoReply, MVideoFullLight } from '../typings/models' | ||
9 | 8 | ||
10 | async function createVideoComment (obj: { | 9 | async function createVideoComment (obj: { |
11 | text: string, | 10 | text: string, |
12 | inReplyToComment: VideoCommentModel | null, | 11 | inReplyToComment: MComment | null, |
13 | video: VideoModel | 12 | video: MVideoFullLight, |
14 | account: AccountModel | 13 | account: MAccountDefault |
15 | }, t: Sequelize.Transaction) { | 14 | }, t: Sequelize.Transaction) { |
16 | let originCommentId: number | null = null | 15 | let originCommentId: number | null = null |
17 | let inReplyToCommentId: number | null = null | 16 | let inReplyToCommentId: number | null = null |
@@ -32,7 +31,7 @@ async function createVideoComment (obj: { | |||
32 | 31 | ||
33 | comment.url = getVideoCommentActivityPubUrl(obj.video, comment) | 32 | comment.url = getVideoCommentActivityPubUrl(obj.video, comment) |
34 | 33 | ||
35 | const savedComment = await comment.save({ transaction: t }) | 34 | const savedComment: MCommentOwnerVideoReply = await comment.save({ transaction: t }) |
36 | savedComment.InReplyToVideoComment = obj.inReplyToComment | 35 | savedComment.InReplyToVideoComment = obj.inReplyToComment |
37 | savedComment.Video = obj.video | 36 | savedComment.Video = obj.video |
38 | savedComment.Account = obj.account | 37 | savedComment.Account = obj.account |
diff --git a/server/lib/video-playlist.ts b/server/lib/video-playlist.ts index 6e214e60f..29b70cfda 100644 --- a/server/lib/video-playlist.ts +++ b/server/lib/video-playlist.ts | |||
@@ -1,12 +1,13 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import { AccountModel } from '../models/account/account' | ||
3 | import { VideoPlaylistModel } from '../models/video/video-playlist' | 2 | import { VideoPlaylistModel } from '../models/video/video-playlist' |
4 | import { VideoPlaylistPrivacy } from '../../shared/models/videos/playlist/video-playlist-privacy.model' | 3 | import { VideoPlaylistPrivacy } from '../../shared/models/videos/playlist/video-playlist-privacy.model' |
5 | import { getVideoPlaylistActivityPubUrl } from './activitypub' | 4 | import { getVideoPlaylistActivityPubUrl } from './activitypub' |
6 | import { VideoPlaylistType } from '../../shared/models/videos/playlist/video-playlist-type.model' | 5 | import { VideoPlaylistType } from '../../shared/models/videos/playlist/video-playlist-type.model' |
6 | import { MAccount } from '../typings/models' | ||
7 | import { MVideoPlaylistOwner } from '../typings/models/video/video-playlist' | ||
7 | 8 | ||
8 | async function createWatchLaterPlaylist (account: AccountModel, t: Sequelize.Transaction) { | 9 | async function createWatchLaterPlaylist (account: MAccount, t: Sequelize.Transaction) { |
9 | const videoPlaylist = new VideoPlaylistModel({ | 10 | const videoPlaylist: MVideoPlaylistOwner = new VideoPlaylistModel({ |
10 | name: 'Watch later', | 11 | name: 'Watch later', |
11 | privacy: VideoPlaylistPrivacy.PRIVATE, | 12 | privacy: VideoPlaylistPrivacy.PRIVATE, |
12 | type: VideoPlaylistType.WATCH_LATER, | 13 | type: VideoPlaylistType.WATCH_LATER, |
diff --git a/server/lib/video-transcoding.ts b/server/lib/video-transcoding.ts index ba6b29163..a204c0c63 100644 --- a/server/lib/video-transcoding.ts +++ b/server/lib/video-transcoding.ts | |||
@@ -5,16 +5,16 @@ import { ensureDir, move, remove, stat } from 'fs-extra' | |||
5 | import { logger } from '../helpers/logger' | 5 | import { logger } from '../helpers/logger' |
6 | import { VideoResolution } from '../../shared/models/videos' | 6 | import { VideoResolution } from '../../shared/models/videos' |
7 | import { VideoFileModel } from '../models/video/video-file' | 7 | import { VideoFileModel } from '../models/video/video-file' |
8 | import { VideoModel } from '../models/video/video' | ||
9 | import { updateMasterHLSPlaylist, updateSha256Segments } from './hls' | 8 | import { updateMasterHLSPlaylist, updateSha256Segments } from './hls' |
10 | import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist' | 9 | import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist' |
11 | import { VideoStreamingPlaylistType } from '../../shared/models/videos/video-streaming-playlist.type' | 10 | import { VideoStreamingPlaylistType } from '../../shared/models/videos/video-streaming-playlist.type' |
12 | import { CONFIG } from '../initializers/config' | 11 | import { CONFIG } from '../initializers/config' |
12 | import { MVideoFile, MVideoWithFile, MVideoWithFileThumbnail } from '@server/typings/models' | ||
13 | 13 | ||
14 | /** | 14 | /** |
15 | * Optimize the original video file and replace it. The resolution is not changed. | 15 | * Optimize the original video file and replace it. The resolution is not changed. |
16 | */ | 16 | */ |
17 | async function optimizeVideofile (video: VideoModel, inputVideoFileArg?: VideoFileModel) { | 17 | async function optimizeVideofile (video: MVideoWithFile, inputVideoFileArg?: MVideoFile) { |
18 | const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR | 18 | const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR |
19 | const transcodeDirectory = CONFIG.STORAGE.TMP_DIR | 19 | const transcodeDirectory = CONFIG.STORAGE.TMP_DIR |
20 | const newExtname = '.mp4' | 20 | const newExtname = '.mp4' |
@@ -57,7 +57,7 @@ async function optimizeVideofile (video: VideoModel, inputVideoFileArg?: VideoFi | |||
57 | /** | 57 | /** |
58 | * Transcode the original video file to a lower resolution. | 58 | * Transcode the original video file to a lower resolution. |
59 | */ | 59 | */ |
60 | async function transcodeOriginalVideofile (video: VideoModel, resolution: VideoResolution, isPortrait: boolean) { | 60 | async function transcodeOriginalVideofile (video: MVideoWithFile, resolution: VideoResolution, isPortrait: boolean) { |
61 | const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR | 61 | const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR |
62 | const transcodeDirectory = CONFIG.STORAGE.TMP_DIR | 62 | const transcodeDirectory = CONFIG.STORAGE.TMP_DIR |
63 | const extname = '.mp4' | 63 | const extname = '.mp4' |
@@ -87,7 +87,7 @@ async function transcodeOriginalVideofile (video: VideoModel, resolution: VideoR | |||
87 | return onVideoFileTranscoding(video, newVideoFile, videoTranscodedPath, videoOutputPath) | 87 | return onVideoFileTranscoding(video, newVideoFile, videoTranscodedPath, videoOutputPath) |
88 | } | 88 | } |
89 | 89 | ||
90 | async function mergeAudioVideofile (video: VideoModel, resolution: VideoResolution) { | 90 | async function mergeAudioVideofile (video: MVideoWithFileThumbnail, resolution: VideoResolution) { |
91 | const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR | 91 | const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR |
92 | const transcodeDirectory = CONFIG.STORAGE.TMP_DIR | 92 | const transcodeDirectory = CONFIG.STORAGE.TMP_DIR |
93 | const newExtname = '.mp4' | 93 | const newExtname = '.mp4' |
@@ -117,7 +117,7 @@ async function mergeAudioVideofile (video: VideoModel, resolution: VideoResoluti | |||
117 | return onVideoFileTranscoding(video, inputVideoFile, videoTranscodedPath, videoOutputPath) | 117 | return onVideoFileTranscoding(video, inputVideoFile, videoTranscodedPath, videoOutputPath) |
118 | } | 118 | } |
119 | 119 | ||
120 | async function generateHlsPlaylist (video: VideoModel, resolution: VideoResolution, isPortraitMode: boolean) { | 120 | async function generateHlsPlaylist (video: MVideoWithFile, resolution: VideoResolution, isPortraitMode: boolean) { |
121 | const baseHlsDirectory = join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid) | 121 | const baseHlsDirectory = join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid) |
122 | await ensureDir(join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid)) | 122 | await ensureDir(join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid)) |
123 | 123 | ||
@@ -165,14 +165,14 @@ export { | |||
165 | 165 | ||
166 | // --------------------------------------------------------------------------- | 166 | // --------------------------------------------------------------------------- |
167 | 167 | ||
168 | async function onVideoFileTranscoding (video: VideoModel, videoFile: VideoFileModel, transcodingPath: string, outputPath: string) { | 168 | async function onVideoFileTranscoding (video: MVideoWithFile, videoFile: MVideoFile, transcodingPath: string, outputPath: string) { |
169 | const stats = await stat(transcodingPath) | 169 | const stats = await stat(transcodingPath) |
170 | const fps = await getVideoFileFPS(transcodingPath) | 170 | const fps = await getVideoFileFPS(transcodingPath) |
171 | 171 | ||
172 | await move(transcodingPath, outputPath) | 172 | await move(transcodingPath, outputPath) |
173 | 173 | ||
174 | videoFile.set('size', stats.size) | 174 | videoFile.size = stats.size |
175 | videoFile.set('fps', fps) | 175 | videoFile.fps = fps |
176 | 176 | ||
177 | await video.createTorrentAndSetInfoHash(videoFile) | 177 | await video.createTorrentAndSetInfoHash(videoFile) |
178 | 178 | ||
diff --git a/server/middlewares/activitypub.ts b/server/middlewares/activitypub.ts index b1e5b5236..bea213d27 100644 --- a/server/middlewares/activitypub.ts +++ b/server/middlewares/activitypub.ts | |||
@@ -101,6 +101,8 @@ async function checkJsonLDSignature (req: Request, res: Response) { | |||
101 | const verified = await isJsonLDSignatureVerified(actor, req.body) | 101 | const verified = await isJsonLDSignatureVerified(actor, req.body) |
102 | 102 | ||
103 | if (verified !== true) { | 103 | if (verified !== true) { |
104 | logger.warn('Signature not verified.', req.body) | ||
105 | |||
104 | res.sendStatus(403) | 106 | res.sendStatus(403) |
105 | return false | 107 | return false |
106 | } | 108 | } |
diff --git a/server/middlewares/validators/follows.ts b/server/middlewares/validators/follows.ts index c3d772297..788735663 100644 --- a/server/middlewares/validators/follows.ts +++ b/server/middlewares/validators/follows.ts | |||
@@ -10,6 +10,7 @@ import { areValidationErrors } from './utils' | |||
10 | import { ActorModel } from '../../models/activitypub/actor' | 10 | import { ActorModel } from '../../models/activitypub/actor' |
11 | import { loadActorUrlOrGetFromWebfinger } from '../../helpers/webfinger' | 11 | import { loadActorUrlOrGetFromWebfinger } from '../../helpers/webfinger' |
12 | import { isValidActorHandle } from '../../helpers/custom-validators/activitypub/actor' | 12 | import { isValidActorHandle } from '../../helpers/custom-validators/activitypub/actor' |
13 | import { MActorFollowActorsDefault } from '@server/typings/models' | ||
13 | 14 | ||
14 | const followValidator = [ | 15 | const followValidator = [ |
15 | body('hosts').custom(isEachUniqueHostValid).withMessage('Should have an array of unique hosts'), | 16 | body('hosts').custom(isEachUniqueHostValid).withMessage('Should have an array of unique hosts'), |
@@ -65,7 +66,7 @@ const getFollowerValidator = [ | |||
65 | 66 | ||
66 | if (areValidationErrors(req, res)) return | 67 | if (areValidationErrors(req, res)) return |
67 | 68 | ||
68 | let follow: ActorFollowModel | 69 | let follow: MActorFollowActorsDefault |
69 | try { | 70 | try { |
70 | const actorUrl = await loadActorUrlOrGetFromWebfinger(req.params.nameWithHost) | 71 | const actorUrl = await loadActorUrlOrGetFromWebfinger(req.params.nameWithHost) |
71 | const actor = await ActorModel.loadByUrl(actorUrl) | 72 | const actor = await ActorModel.loadByUrl(actorUrl) |
diff --git a/server/middlewares/validators/redundancy.ts b/server/middlewares/validators/redundancy.ts index 1fdac0e4e..e65d3b8d3 100644 --- a/server/middlewares/validators/redundancy.ts +++ b/server/middlewares/validators/redundancy.ts | |||
@@ -24,7 +24,7 @@ const videoFileRedundancyGetValidator = [ | |||
24 | if (areValidationErrors(req, res)) return | 24 | if (areValidationErrors(req, res)) return |
25 | if (!await doesVideoExist(req.params.videoId, res)) return | 25 | if (!await doesVideoExist(req.params.videoId, res)) return |
26 | 26 | ||
27 | const video = res.locals.video | 27 | const video = res.locals.videoAll |
28 | const videoFile = video.VideoFiles.find(f => { | 28 | const videoFile = video.VideoFiles.find(f => { |
29 | return f.resolution === req.params.resolution && (!req.params.fps || f.fps === req.params.fps) | 29 | return f.resolution === req.params.resolution && (!req.params.fps || f.fps === req.params.fps) |
30 | }) | 30 | }) |
@@ -50,7 +50,7 @@ const videoPlaylistRedundancyGetValidator = [ | |||
50 | if (areValidationErrors(req, res)) return | 50 | if (areValidationErrors(req, res)) return |
51 | if (!await doesVideoExist(req.params.videoId, res)) return | 51 | if (!await doesVideoExist(req.params.videoId, res)) return |
52 | 52 | ||
53 | const video = res.locals.video | 53 | const video = res.locals.videoAll |
54 | const videoStreamingPlaylist = video.VideoStreamingPlaylists.find(p => p === req.params.streamingPlaylistType) | 54 | const videoStreamingPlaylist = video.VideoStreamingPlaylists.find(p => p === req.params.streamingPlaylistType) |
55 | 55 | ||
56 | if (!videoStreamingPlaylist) return res.status(404).json({ error: 'Video playlist not found.' }) | 56 | if (!videoStreamingPlaylist) return res.status(404).json({ error: 'Video playlist not found.' }) |
diff --git a/server/middlewares/validators/user-notifications.ts b/server/middlewares/validators/user-notifications.ts index 308b32655..fbfcb0a4c 100644 --- a/server/middlewares/validators/user-notifications.ts +++ b/server/middlewares/validators/user-notifications.ts | |||
@@ -43,6 +43,8 @@ const updateNotificationSettingsValidator = [ | |||
43 | .custom(isUserNotificationSettingValid).withMessage('Should have a valid new user registration notification setting'), | 43 | .custom(isUserNotificationSettingValid).withMessage('Should have a valid new user registration notification setting'), |
44 | body('newInstanceFollower') | 44 | body('newInstanceFollower') |
45 | .custom(isUserNotificationSettingValid).withMessage('Should have a valid new instance follower notification setting'), | 45 | .custom(isUserNotificationSettingValid).withMessage('Should have a valid new instance follower notification setting'), |
46 | body('autoInstanceFollowing') | ||
47 | .custom(isUserNotificationSettingValid).withMessage('Should have a valid new instance following notification setting'), | ||
46 | 48 | ||
47 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | 49 | (req: express.Request, res: express.Response, next: express.NextFunction) => { |
48 | logger.debug('Checking updateNotificationSettingsValidator parameters', { parameters: req.body }) | 50 | logger.debug('Checking updateNotificationSettingsValidator parameters', { parameters: req.body }) |
diff --git a/server/middlewares/validators/users.ts b/server/middlewares/validators/users.ts index 8ee2ec1f5..544db76d7 100644 --- a/server/middlewares/validators/users.ts +++ b/server/middlewares/validators/users.ts | |||
@@ -4,6 +4,7 @@ import { body, param } from 'express-validator' | |||
4 | import { omit } from 'lodash' | 4 | import { omit } from 'lodash' |
5 | import { isIdOrUUIDValid, toBooleanOrNull, toIntOrNull } from '../../helpers/custom-validators/misc' | 5 | import { isIdOrUUIDValid, toBooleanOrNull, toIntOrNull } from '../../helpers/custom-validators/misc' |
6 | import { | 6 | import { |
7 | isNoInstanceConfigWarningModal, isNoWelcomeModal, | ||
7 | isUserAdminFlagsValid, | 8 | isUserAdminFlagsValid, |
8 | isUserAutoPlayVideoValid, | 9 | isUserAutoPlayVideoValid, |
9 | isUserBlockedReasonValid, | 10 | isUserBlockedReasonValid, |
@@ -31,6 +32,7 @@ import { isThemeNameValid } from '../../helpers/custom-validators/plugins' | |||
31 | import { isThemeRegistered } from '../../lib/plugins/theme-utils' | 32 | import { isThemeRegistered } from '../../lib/plugins/theme-utils' |
32 | import { doesVideoExist } from '../../helpers/middlewares' | 33 | import { doesVideoExist } from '../../helpers/middlewares' |
33 | import { UserRole } from '../../../shared/models/users' | 34 | import { UserRole } from '../../../shared/models/users' |
35 | import { MUserDefault } from '@server/typings/models' | ||
34 | 36 | ||
35 | const usersAddValidator = [ | 37 | const usersAddValidator = [ |
36 | body('username').custom(isUserUsernameValid).withMessage('Should have a valid username (lowercase alphanumeric characters)'), | 38 | body('username').custom(isUserUsernameValid).withMessage('Should have a valid username (lowercase alphanumeric characters)'), |
@@ -215,6 +217,12 @@ const usersUpdateMeValidator = [ | |||
215 | body('theme') | 217 | body('theme') |
216 | .optional() | 218 | .optional() |
217 | .custom(v => isThemeNameValid(v) && isThemeRegistered(v)).withMessage('Should have a valid theme'), | 219 | .custom(v => isThemeNameValid(v) && isThemeRegistered(v)).withMessage('Should have a valid theme'), |
220 | body('noInstanceConfigWarningModal') | ||
221 | .optional() | ||
222 | .custom(v => isNoInstanceConfigWarningModal(v)).withMessage('Should have a valid noInstanceConfigWarningModal boolean'), | ||
223 | body('noWelcomeModal') | ||
224 | .optional() | ||
225 | .custom(v => isNoWelcomeModal(v)).withMessage('Should have a valid noWelcomeModal boolean'), | ||
218 | 226 | ||
219 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 227 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
220 | logger.debug('Checking usersUpdateMe parameters', { parameters: omit(req.body, 'password') }) | 228 | logger.debug('Checking usersUpdateMe parameters', { parameters: omit(req.body, 'password') }) |
@@ -462,7 +470,7 @@ async function checkUserNameOrEmailDoesNotAlreadyExist (username: string, email: | |||
462 | return true | 470 | return true |
463 | } | 471 | } |
464 | 472 | ||
465 | async function checkUserExist (finder: () => Bluebird<UserModel>, res: express.Response, abortResponse = true) { | 473 | async function checkUserExist (finder: () => Bluebird<MUserDefault>, res: express.Response, abortResponse = true) { |
466 | const user = await finder() | 474 | const user = await finder() |
467 | 475 | ||
468 | if (!user) { | 476 | if (!user) { |
diff --git a/server/middlewares/validators/videos/video-abuses.ts b/server/middlewares/validators/videos/video-abuses.ts index e27d91bb1..a4aef4024 100644 --- a/server/middlewares/validators/videos/video-abuses.ts +++ b/server/middlewares/validators/videos/video-abuses.ts | |||
@@ -33,7 +33,7 @@ const videoAbuseGetValidator = [ | |||
33 | 33 | ||
34 | if (areValidationErrors(req, res)) return | 34 | if (areValidationErrors(req, res)) return |
35 | if (!await doesVideoExist(req.params.videoId, res)) return | 35 | if (!await doesVideoExist(req.params.videoId, res)) return |
36 | if (!await doesVideoAbuseExist(req.params.id, res.locals.video.id, res)) return | 36 | if (!await doesVideoAbuseExist(req.params.id, res.locals.videoAll.id, res)) return |
37 | 37 | ||
38 | return next() | 38 | return next() |
39 | } | 39 | } |
@@ -54,7 +54,7 @@ const videoAbuseUpdateValidator = [ | |||
54 | 54 | ||
55 | if (areValidationErrors(req, res)) return | 55 | if (areValidationErrors(req, res)) return |
56 | if (!await doesVideoExist(req.params.videoId, res)) return | 56 | if (!await doesVideoExist(req.params.videoId, res)) return |
57 | if (!await doesVideoAbuseExist(req.params.id, res.locals.video.id, res)) return | 57 | if (!await doesVideoAbuseExist(req.params.id, res.locals.videoAll.id, res)) return |
58 | 58 | ||
59 | return next() | 59 | return next() |
60 | } | 60 | } |
diff --git a/server/middlewares/validators/videos/video-blacklist.ts b/server/middlewares/validators/videos/video-blacklist.ts index 3e8c5b30c..5440e57e7 100644 --- a/server/middlewares/validators/videos/video-blacklist.ts +++ b/server/middlewares/validators/videos/video-blacklist.ts | |||
@@ -14,7 +14,7 @@ const videosBlacklistRemoveValidator = [ | |||
14 | 14 | ||
15 | if (areValidationErrors(req, res)) return | 15 | if (areValidationErrors(req, res)) return |
16 | if (!await doesVideoExist(req.params.videoId, res)) return | 16 | if (!await doesVideoExist(req.params.videoId, res)) return |
17 | if (!await doesVideoBlacklistExist(res.locals.video.id, res)) return | 17 | if (!await doesVideoBlacklistExist(res.locals.videoAll.id, res)) return |
18 | 18 | ||
19 | return next() | 19 | return next() |
20 | } | 20 | } |
@@ -36,7 +36,7 @@ const videosBlacklistAddValidator = [ | |||
36 | if (areValidationErrors(req, res)) return | 36 | if (areValidationErrors(req, res)) return |
37 | if (!await doesVideoExist(req.params.videoId, res)) return | 37 | if (!await doesVideoExist(req.params.videoId, res)) return |
38 | 38 | ||
39 | const video = res.locals.video | 39 | const video = res.locals.videoAll |
40 | if (req.body.unfederate === true && video.remote === true) { | 40 | if (req.body.unfederate === true && video.remote === true) { |
41 | return res | 41 | return res |
42 | .status(409) | 42 | .status(409) |
@@ -59,7 +59,7 @@ const videosBlacklistUpdateValidator = [ | |||
59 | 59 | ||
60 | if (areValidationErrors(req, res)) return | 60 | if (areValidationErrors(req, res)) return |
61 | if (!await doesVideoExist(req.params.videoId, res)) return | 61 | if (!await doesVideoExist(req.params.videoId, res)) return |
62 | if (!await doesVideoBlacklistExist(res.locals.video.id, res)) return | 62 | if (!await doesVideoBlacklistExist(res.locals.videoAll.id, res)) return |
63 | 63 | ||
64 | return next() | 64 | return next() |
65 | } | 65 | } |
diff --git a/server/middlewares/validators/videos/video-captions.ts b/server/middlewares/validators/videos/video-captions.ts index f5610222a..2fb1da5ce 100644 --- a/server/middlewares/validators/videos/video-captions.ts +++ b/server/middlewares/validators/videos/video-captions.ts | |||
@@ -26,7 +26,7 @@ const addVideoCaptionValidator = [ | |||
26 | 26 | ||
27 | // Check if the user who did the request is able to update the video | 27 | // Check if the user who did the request is able to update the video |
28 | const user = res.locals.oauth.token.User | 28 | const user = res.locals.oauth.token.User |
29 | if (!checkUserCanManageVideo(user, res.locals.video, UserRight.UPDATE_ANY_VIDEO, res)) return cleanUpReqFiles(req) | 29 | if (!checkUserCanManageVideo(user, res.locals.videoAll, UserRight.UPDATE_ANY_VIDEO, res)) return cleanUpReqFiles(req) |
30 | 30 | ||
31 | return next() | 31 | return next() |
32 | } | 32 | } |
@@ -41,11 +41,11 @@ const deleteVideoCaptionValidator = [ | |||
41 | 41 | ||
42 | if (areValidationErrors(req, res)) return | 42 | if (areValidationErrors(req, res)) return |
43 | if (!await doesVideoExist(req.params.videoId, res)) return | 43 | if (!await doesVideoExist(req.params.videoId, res)) return |
44 | if (!await doesVideoCaptionExist(res.locals.video, req.params.captionLanguage, res)) return | 44 | if (!await doesVideoCaptionExist(res.locals.videoAll, req.params.captionLanguage, res)) return |
45 | 45 | ||
46 | // Check if the user who did the request is able to update the video | 46 | // Check if the user who did the request is able to update the video |
47 | const user = res.locals.oauth.token.User | 47 | const user = res.locals.oauth.token.User |
48 | if (!checkUserCanManageVideo(user, res.locals.video, UserRight.UPDATE_ANY_VIDEO, res)) return | 48 | if (!checkUserCanManageVideo(user, res.locals.videoAll, UserRight.UPDATE_ANY_VIDEO, res)) return |
49 | 49 | ||
50 | return next() | 50 | return next() |
51 | } | 51 | } |
diff --git a/server/middlewares/validators/videos/video-channels.ts b/server/middlewares/validators/videos/video-channels.ts index 3ee5064fc..d21274527 100644 --- a/server/middlewares/validators/videos/video-channels.ts +++ b/server/middlewares/validators/videos/video-channels.ts | |||
@@ -7,13 +7,13 @@ import { | |||
7 | isVideoChannelSupportValid | 7 | isVideoChannelSupportValid |
8 | } from '../../../helpers/custom-validators/video-channels' | 8 | } from '../../../helpers/custom-validators/video-channels' |
9 | import { logger } from '../../../helpers/logger' | 9 | import { logger } from '../../../helpers/logger' |
10 | import { UserModel } from '../../../models/account/user' | ||
11 | import { VideoChannelModel } from '../../../models/video/video-channel' | 10 | import { VideoChannelModel } from '../../../models/video/video-channel' |
12 | import { areValidationErrors } from '../utils' | 11 | import { areValidationErrors } from '../utils' |
13 | import { isActorPreferredUsernameValid } from '../../../helpers/custom-validators/activitypub/actor' | 12 | import { isActorPreferredUsernameValid } from '../../../helpers/custom-validators/activitypub/actor' |
14 | import { ActorModel } from '../../../models/activitypub/actor' | 13 | import { ActorModel } from '../../../models/activitypub/actor' |
15 | import { isBooleanValid } from '../../../helpers/custom-validators/misc' | 14 | import { isBooleanValid } from '../../../helpers/custom-validators/misc' |
16 | import { doesLocalVideoChannelNameExist, doesVideoChannelNameWithHostExist } from '../../../helpers/middlewares' | 15 | import { doesLocalVideoChannelNameExist, doesVideoChannelNameWithHostExist } from '../../../helpers/middlewares' |
16 | import { MChannelAccountDefault, MUser } from '@server/typings/models' | ||
17 | 17 | ||
18 | const videoChannelsAddValidator = [ | 18 | const videoChannelsAddValidator = [ |
19 | body('name').custom(isActorPreferredUsernameValid).withMessage('Should have a valid channel name'), | 19 | body('name').custom(isActorPreferredUsernameValid).withMessage('Should have a valid channel name'), |
@@ -131,7 +131,7 @@ export { | |||
131 | 131 | ||
132 | // --------------------------------------------------------------------------- | 132 | // --------------------------------------------------------------------------- |
133 | 133 | ||
134 | function checkUserCanDeleteVideoChannel (user: UserModel, videoChannel: VideoChannelModel, res: express.Response) { | 134 | function checkUserCanDeleteVideoChannel (user: MUser, videoChannel: MChannelAccountDefault, res: express.Response) { |
135 | if (videoChannel.Actor.isOwned() === false) { | 135 | if (videoChannel.Actor.isOwned() === false) { |
136 | res.status(403) | 136 | res.status(403) |
137 | .json({ error: 'Cannot remove video channel of another server.' }) | 137 | .json({ error: 'Cannot remove video channel of another server.' }) |
diff --git a/server/middlewares/validators/videos/video-comments.ts b/server/middlewares/validators/videos/video-comments.ts index 83a0c24b0..8adbb02ba 100644 --- a/server/middlewares/validators/videos/video-comments.ts +++ b/server/middlewares/validators/videos/video-comments.ts | |||
@@ -4,13 +4,13 @@ import { UserRight } from '../../../../shared' | |||
4 | import { isIdOrUUIDValid, isIdValid } from '../../../helpers/custom-validators/misc' | 4 | import { isIdOrUUIDValid, isIdValid } from '../../../helpers/custom-validators/misc' |
5 | import { isValidVideoCommentText } from '../../../helpers/custom-validators/video-comments' | 5 | import { isValidVideoCommentText } from '../../../helpers/custom-validators/video-comments' |
6 | import { logger } from '../../../helpers/logger' | 6 | import { logger } from '../../../helpers/logger' |
7 | import { UserModel } from '../../../models/account/user' | ||
8 | import { VideoModel } from '../../../models/video/video' | ||
9 | import { VideoCommentModel } from '../../../models/video/video-comment' | 7 | import { VideoCommentModel } from '../../../models/video/video-comment' |
10 | import { areValidationErrors } from '../utils' | 8 | import { areValidationErrors } from '../utils' |
11 | import { Hooks } from '../../../lib/plugins/hooks' | 9 | import { Hooks } from '../../../lib/plugins/hooks' |
12 | import { isLocalVideoThreadAccepted, isLocalVideoCommentReplyAccepted, AcceptResult } from '../../../lib/moderation' | 10 | import { AcceptResult, isLocalVideoCommentReplyAccepted, isLocalVideoThreadAccepted } from '../../../lib/moderation' |
13 | import { doesVideoExist } from '../../../helpers/middlewares' | 11 | import { doesVideoExist } from '../../../helpers/middlewares' |
12 | import { MCommentOwner, MVideo, MVideoFullLight, MVideoId } from '../../../typings/models/video' | ||
13 | import { MUser } from '@server/typings/models' | ||
14 | 14 | ||
15 | const listVideoCommentThreadsValidator = [ | 15 | const listVideoCommentThreadsValidator = [ |
16 | param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), | 16 | param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), |
@@ -34,7 +34,7 @@ const listVideoThreadCommentsValidator = [ | |||
34 | 34 | ||
35 | if (areValidationErrors(req, res)) return | 35 | if (areValidationErrors(req, res)) return |
36 | if (!await doesVideoExist(req.params.videoId, res, 'only-video')) return | 36 | if (!await doesVideoExist(req.params.videoId, res, 'only-video')) return |
37 | if (!await doesVideoCommentThreadExist(req.params.threadId, res.locals.video, res)) return | 37 | if (!await doesVideoCommentThreadExist(req.params.threadId, res.locals.onlyVideo, res)) return |
38 | 38 | ||
39 | return next() | 39 | return next() |
40 | } | 40 | } |
@@ -49,8 +49,8 @@ const addVideoCommentThreadValidator = [ | |||
49 | 49 | ||
50 | if (areValidationErrors(req, res)) return | 50 | if (areValidationErrors(req, res)) return |
51 | if (!await doesVideoExist(req.params.videoId, res)) return | 51 | if (!await doesVideoExist(req.params.videoId, res)) return |
52 | if (!isVideoCommentsEnabled(res.locals.video, res)) return | 52 | if (!isVideoCommentsEnabled(res.locals.videoAll, res)) return |
53 | if (!await isVideoCommentAccepted(req, res, false)) return | 53 | if (!await isVideoCommentAccepted(req, res, res.locals.videoAll,false)) return |
54 | 54 | ||
55 | return next() | 55 | return next() |
56 | } | 56 | } |
@@ -66,9 +66,9 @@ const addVideoCommentReplyValidator = [ | |||
66 | 66 | ||
67 | if (areValidationErrors(req, res)) return | 67 | if (areValidationErrors(req, res)) return |
68 | if (!await doesVideoExist(req.params.videoId, res)) return | 68 | if (!await doesVideoExist(req.params.videoId, res)) return |
69 | if (!isVideoCommentsEnabled(res.locals.video, res)) return | 69 | if (!isVideoCommentsEnabled(res.locals.videoAll, res)) return |
70 | if (!await doesVideoCommentExist(req.params.commentId, res.locals.video, res)) return | 70 | if (!await doesVideoCommentExist(req.params.commentId, res.locals.videoAll, res)) return |
71 | if (!await isVideoCommentAccepted(req, res, true)) return | 71 | if (!await isVideoCommentAccepted(req, res, res.locals.videoAll, true)) return |
72 | 72 | ||
73 | return next() | 73 | return next() |
74 | } | 74 | } |
@@ -83,7 +83,7 @@ const videoCommentGetValidator = [ | |||
83 | 83 | ||
84 | if (areValidationErrors(req, res)) return | 84 | if (areValidationErrors(req, res)) return |
85 | if (!await doesVideoExist(req.params.videoId, res, 'id')) return | 85 | if (!await doesVideoExist(req.params.videoId, res, 'id')) return |
86 | if (!await doesVideoCommentExist(req.params.commentId, res.locals.video, res)) return | 86 | if (!await doesVideoCommentExist(req.params.commentId, res.locals.videoId, res)) return |
87 | 87 | ||
88 | return next() | 88 | return next() |
89 | } | 89 | } |
@@ -98,10 +98,10 @@ const removeVideoCommentValidator = [ | |||
98 | 98 | ||
99 | if (areValidationErrors(req, res)) return | 99 | if (areValidationErrors(req, res)) return |
100 | if (!await doesVideoExist(req.params.videoId, res)) return | 100 | if (!await doesVideoExist(req.params.videoId, res)) return |
101 | if (!await doesVideoCommentExist(req.params.commentId, res.locals.video, res)) return | 101 | if (!await doesVideoCommentExist(req.params.commentId, res.locals.videoAll, res)) return |
102 | 102 | ||
103 | // Check if the user who did the request is able to delete the video | 103 | // Check if the user who did the request is able to delete the video |
104 | if (!checkUserCanDeleteVideoComment(res.locals.oauth.token.User, res.locals.videoComment, res)) return | 104 | if (!checkUserCanDeleteVideoComment(res.locals.oauth.token.User, res.locals.videoCommentFull, res)) return |
105 | 105 | ||
106 | return next() | 106 | return next() |
107 | } | 107 | } |
@@ -120,7 +120,7 @@ export { | |||
120 | 120 | ||
121 | // --------------------------------------------------------------------------- | 121 | // --------------------------------------------------------------------------- |
122 | 122 | ||
123 | async function doesVideoCommentThreadExist (id: number, video: VideoModel, res: express.Response) { | 123 | async function doesVideoCommentThreadExist (id: number, video: MVideoId, res: express.Response) { |
124 | const videoComment = await VideoCommentModel.loadById(id) | 124 | const videoComment = await VideoCommentModel.loadById(id) |
125 | 125 | ||
126 | if (!videoComment) { | 126 | if (!videoComment) { |
@@ -151,7 +151,7 @@ async function doesVideoCommentThreadExist (id: number, video: VideoModel, res: | |||
151 | return true | 151 | return true |
152 | } | 152 | } |
153 | 153 | ||
154 | async function doesVideoCommentExist (id: number, video: VideoModel, res: express.Response) { | 154 | async function doesVideoCommentExist (id: number, video: MVideoId, res: express.Response) { |
155 | const videoComment = await VideoCommentModel.loadByIdAndPopulateVideoAndAccountAndReply(id) | 155 | const videoComment = await VideoCommentModel.loadByIdAndPopulateVideoAndAccountAndReply(id) |
156 | 156 | ||
157 | if (!videoComment) { | 157 | if (!videoComment) { |
@@ -170,11 +170,11 @@ async function doesVideoCommentExist (id: number, video: VideoModel, res: expres | |||
170 | return false | 170 | return false |
171 | } | 171 | } |
172 | 172 | ||
173 | res.locals.videoComment = videoComment | 173 | res.locals.videoCommentFull = videoComment |
174 | return true | 174 | return true |
175 | } | 175 | } |
176 | 176 | ||
177 | function isVideoCommentsEnabled (video: VideoModel, res: express.Response) { | 177 | function isVideoCommentsEnabled (video: MVideo, res: express.Response) { |
178 | if (video.commentsEnabled !== true) { | 178 | if (video.commentsEnabled !== true) { |
179 | res.status(409) | 179 | res.status(409) |
180 | .json({ error: 'Video comments are disabled for this video.' }) | 180 | .json({ error: 'Video comments are disabled for this video.' }) |
@@ -186,7 +186,7 @@ function isVideoCommentsEnabled (video: VideoModel, res: express.Response) { | |||
186 | return true | 186 | return true |
187 | } | 187 | } |
188 | 188 | ||
189 | function checkUserCanDeleteVideoComment (user: UserModel, videoComment: VideoCommentModel, res: express.Response) { | 189 | function checkUserCanDeleteVideoComment (user: MUser, videoComment: MCommentOwner, res: express.Response) { |
190 | const account = videoComment.Account | 190 | const account = videoComment.Account |
191 | if (user.hasRight(UserRight.REMOVE_ANY_VIDEO_COMMENT) === false && account.userId !== user.id) { | 191 | if (user.hasRight(UserRight.REMOVE_ANY_VIDEO_COMMENT) === false && account.userId !== user.id) { |
192 | res.status(403) | 192 | res.status(403) |
@@ -198,9 +198,9 @@ function checkUserCanDeleteVideoComment (user: UserModel, videoComment: VideoCom | |||
198 | return true | 198 | return true |
199 | } | 199 | } |
200 | 200 | ||
201 | async function isVideoCommentAccepted (req: express.Request, res: express.Response, isReply: boolean) { | 201 | async function isVideoCommentAccepted (req: express.Request, res: express.Response, video: MVideoFullLight, isReply: boolean) { |
202 | const acceptParameters = { | 202 | const acceptParameters = { |
203 | video: res.locals.video, | 203 | video, |
204 | commentBody: req.body, | 204 | commentBody: req.body, |
205 | user: res.locals.oauth.token.User | 205 | user: res.locals.oauth.token.User |
206 | } | 206 | } |
@@ -208,7 +208,7 @@ async function isVideoCommentAccepted (req: express.Request, res: express.Respon | |||
208 | let acceptedResult: AcceptResult | 208 | let acceptedResult: AcceptResult |
209 | 209 | ||
210 | if (isReply) { | 210 | if (isReply) { |
211 | const acceptReplyParameters = Object.assign(acceptParameters, { parentComment: res.locals.videoComment }) | 211 | const acceptReplyParameters = Object.assign(acceptParameters, { parentComment: res.locals.videoCommentFull }) |
212 | 212 | ||
213 | acceptedResult = await Hooks.wrapFun( | 213 | acceptedResult = await Hooks.wrapFun( |
214 | isLocalVideoCommentReplyAccepted, | 214 | isLocalVideoCommentReplyAccepted, |
diff --git a/server/middlewares/validators/videos/video-playlists.ts b/server/middlewares/validators/videos/video-playlists.ts index 5823795be..27ee62b1f 100644 --- a/server/middlewares/validators/videos/video-playlists.ts +++ b/server/middlewares/validators/videos/video-playlists.ts | |||
@@ -2,7 +2,6 @@ import * as express from 'express' | |||
2 | import { body, param, query, ValidationChain } from 'express-validator' | 2 | import { body, param, query, ValidationChain } from 'express-validator' |
3 | import { UserRight, VideoPlaylistCreate, VideoPlaylistUpdate } from '../../../../shared' | 3 | import { UserRight, VideoPlaylistCreate, VideoPlaylistUpdate } from '../../../../shared' |
4 | import { logger } from '../../../helpers/logger' | 4 | import { logger } from '../../../helpers/logger' |
5 | import { UserModel } from '../../../models/account/user' | ||
6 | import { areValidationErrors } from '../utils' | 5 | import { areValidationErrors } from '../utils' |
7 | import { isVideoImage } from '../../../helpers/custom-validators/videos' | 6 | import { isVideoImage } from '../../../helpers/custom-validators/videos' |
8 | import { CONSTRAINTS_FIELDS } from '../../../initializers/constants' | 7 | import { CONSTRAINTS_FIELDS } from '../../../initializers/constants' |
@@ -22,13 +21,14 @@ import { | |||
22 | isVideoPlaylistTimestampValid, | 21 | isVideoPlaylistTimestampValid, |
23 | isVideoPlaylistTypeValid | 22 | isVideoPlaylistTypeValid |
24 | } from '../../../helpers/custom-validators/video-playlists' | 23 | } from '../../../helpers/custom-validators/video-playlists' |
25 | import { VideoPlaylistModel } from '../../../models/video/video-playlist' | ||
26 | import { cleanUpReqFiles } from '../../../helpers/express-utils' | 24 | import { cleanUpReqFiles } from '../../../helpers/express-utils' |
27 | import { VideoPlaylistElementModel } from '../../../models/video/video-playlist-element' | 25 | import { VideoPlaylistElementModel } from '../../../models/video/video-playlist-element' |
28 | import { authenticatePromiseIfNeeded } from '../../oauth' | 26 | import { authenticatePromiseIfNeeded } from '../../oauth' |
29 | import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model' | 27 | import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model' |
30 | import { VideoPlaylistType } from '../../../../shared/models/videos/playlist/video-playlist-type.model' | 28 | import { VideoPlaylistType } from '../../../../shared/models/videos/playlist/video-playlist-type.model' |
31 | import { doesVideoChannelIdExist, doesVideoExist, doesVideoPlaylistExist } from '../../../helpers/middlewares' | 29 | import { doesVideoChannelIdExist, doesVideoExist, doesVideoPlaylistExist, VideoPlaylistFetchType } from '../../../helpers/middlewares' |
30 | import { MVideoPlaylist } from '../../../typings/models/video/video-playlist' | ||
31 | import { MUserAccountId } from '@server/typings/models' | ||
32 | 32 | ||
33 | const videoPlaylistsAddValidator = getCommonPlaylistEditAttributes().concat([ | 33 | const videoPlaylistsAddValidator = getCommonPlaylistEditAttributes().concat([ |
34 | body('displayName') | 34 | body('displayName') |
@@ -67,9 +67,9 @@ const videoPlaylistsUpdateValidator = getCommonPlaylistEditAttributes().concat([ | |||
67 | 67 | ||
68 | if (!await doesVideoPlaylistExist(req.params.playlistId, res, 'all')) return cleanUpReqFiles(req) | 68 | if (!await doesVideoPlaylistExist(req.params.playlistId, res, 'all')) return cleanUpReqFiles(req) |
69 | 69 | ||
70 | const videoPlaylist = res.locals.videoPlaylist | 70 | const videoPlaylist = getPlaylist(res) |
71 | 71 | ||
72 | if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, res.locals.videoPlaylist, UserRight.REMOVE_ANY_VIDEO_PLAYLIST, res)) { | 72 | if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, videoPlaylist, UserRight.REMOVE_ANY_VIDEO_PLAYLIST, res)) { |
73 | return cleanUpReqFiles(req) | 73 | return cleanUpReqFiles(req) |
74 | } | 74 | } |
75 | 75 | ||
@@ -110,13 +110,13 @@ const videoPlaylistsDeleteValidator = [ | |||
110 | 110 | ||
111 | if (!await doesVideoPlaylistExist(req.params.playlistId, res)) return | 111 | if (!await doesVideoPlaylistExist(req.params.playlistId, res)) return |
112 | 112 | ||
113 | const videoPlaylist = res.locals.videoPlaylist | 113 | const videoPlaylist = getPlaylist(res) |
114 | if (videoPlaylist.type === VideoPlaylistType.WATCH_LATER) { | 114 | if (videoPlaylist.type === VideoPlaylistType.WATCH_LATER) { |
115 | return res.status(400) | 115 | return res.status(400) |
116 | .json({ error: 'Cannot delete a watch later playlist.' }) | 116 | .json({ error: 'Cannot delete a watch later playlist.' }) |
117 | } | 117 | } |
118 | 118 | ||
119 | if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, res.locals.videoPlaylist, UserRight.REMOVE_ANY_VIDEO_PLAYLIST, res)) { | 119 | if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, videoPlaylist, UserRight.REMOVE_ANY_VIDEO_PLAYLIST, res)) { |
120 | return | 120 | return |
121 | } | 121 | } |
122 | 122 | ||
@@ -124,45 +124,47 @@ const videoPlaylistsDeleteValidator = [ | |||
124 | } | 124 | } |
125 | ] | 125 | ] |
126 | 126 | ||
127 | const videoPlaylistsGetValidator = [ | 127 | const videoPlaylistsGetValidator = (fetchType: VideoPlaylistFetchType) => { |
128 | param('playlistId') | 128 | return [ |
129 | .custom(isIdOrUUIDValid).withMessage('Should have a valid playlist id/uuid'), | 129 | param('playlistId') |
130 | .custom(isIdOrUUIDValid).withMessage('Should have a valid playlist id/uuid'), | ||
130 | 131 | ||
131 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 132 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
132 | logger.debug('Checking videoPlaylistsGetValidator parameters', { parameters: req.params }) | 133 | logger.debug('Checking videoPlaylistsGetValidator parameters', { parameters: req.params }) |
133 | 134 | ||
134 | if (areValidationErrors(req, res)) return | 135 | if (areValidationErrors(req, res)) return |
135 | 136 | ||
136 | if (!await doesVideoPlaylistExist(req.params.playlistId, res)) return | 137 | if (!await doesVideoPlaylistExist(req.params.playlistId, res, fetchType)) return |
137 | 138 | ||
138 | const videoPlaylist = res.locals.videoPlaylist | 139 | const videoPlaylist = res.locals.videoPlaylistFull || res.locals.videoPlaylistSummary |
139 | 140 | ||
140 | // Video is unlisted, check we used the uuid to fetch it | 141 | // Video is unlisted, check we used the uuid to fetch it |
141 | if (videoPlaylist.privacy === VideoPlaylistPrivacy.UNLISTED) { | 142 | if (videoPlaylist.privacy === VideoPlaylistPrivacy.UNLISTED) { |
142 | if (isUUIDValid(req.params.playlistId)) return next() | 143 | if (isUUIDValid(req.params.playlistId)) return next() |
143 | 144 | ||
144 | return res.status(404).end() | 145 | return res.status(404).end() |
145 | } | 146 | } |
147 | |||
148 | if (videoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) { | ||
149 | await authenticatePromiseIfNeeded(req, res) | ||
146 | 150 | ||
147 | if (videoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) { | 151 | const user = res.locals.oauth ? res.locals.oauth.token.User : null |
148 | await authenticatePromiseIfNeeded(req, res) | ||
149 | 152 | ||
150 | const user = res.locals.oauth ? res.locals.oauth.token.User : null | 153 | if ( |
154 | !user || | ||
155 | (videoPlaylist.OwnerAccount.id !== user.Account.id && !user.hasRight(UserRight.UPDATE_ANY_VIDEO_PLAYLIST)) | ||
156 | ) { | ||
157 | return res.status(403) | ||
158 | .json({ error: 'Cannot get this private video playlist.' }) | ||
159 | } | ||
151 | 160 | ||
152 | if ( | 161 | return next() |
153 | !user || | ||
154 | (videoPlaylist.OwnerAccount.id !== user.Account.id && !user.hasRight(UserRight.UPDATE_ANY_VIDEO_PLAYLIST)) | ||
155 | ) { | ||
156 | return res.status(403) | ||
157 | .json({ error: 'Cannot get this private video playlist.' }) | ||
158 | } | 162 | } |
159 | 163 | ||
160 | return next() | 164 | return next() |
161 | } | 165 | } |
162 | 166 | ] | |
163 | return next() | 167 | } |
164 | } | ||
165 | ] | ||
166 | 168 | ||
167 | const videoPlaylistsAddVideoValidator = [ | 169 | const videoPlaylistsAddVideoValidator = [ |
168 | param('playlistId') | 170 | param('playlistId') |
@@ -184,8 +186,8 @@ const videoPlaylistsAddVideoValidator = [ | |||
184 | if (!await doesVideoPlaylistExist(req.params.playlistId, res, 'all')) return | 186 | if (!await doesVideoPlaylistExist(req.params.playlistId, res, 'all')) return |
185 | if (!await doesVideoExist(req.body.videoId, res, 'only-video')) return | 187 | if (!await doesVideoExist(req.body.videoId, res, 'only-video')) return |
186 | 188 | ||
187 | const videoPlaylist = res.locals.videoPlaylist | 189 | const videoPlaylist = getPlaylist(res) |
188 | const video = res.locals.video | 190 | const video = res.locals.onlyVideo |
189 | 191 | ||
190 | const videoPlaylistElement = await VideoPlaylistElementModel.loadByPlaylistAndVideo(videoPlaylist.id, video.id) | 192 | const videoPlaylistElement = await VideoPlaylistElementModel.loadByPlaylistAndVideo(videoPlaylist.id, video.id) |
191 | if (videoPlaylistElement) { | 193 | if (videoPlaylistElement) { |
@@ -196,7 +198,7 @@ const videoPlaylistsAddVideoValidator = [ | |||
196 | return | 198 | return |
197 | } | 199 | } |
198 | 200 | ||
199 | if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, res.locals.videoPlaylist, UserRight.UPDATE_ANY_VIDEO_PLAYLIST, res)) { | 201 | if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, videoPlaylist, UserRight.UPDATE_ANY_VIDEO_PLAYLIST, res)) { |
200 | return | 202 | return |
201 | } | 203 | } |
202 | 204 | ||
@@ -223,7 +225,7 @@ const videoPlaylistsUpdateOrRemoveVideoValidator = [ | |||
223 | 225 | ||
224 | if (!await doesVideoPlaylistExist(req.params.playlistId, res, 'all')) return | 226 | if (!await doesVideoPlaylistExist(req.params.playlistId, res, 'all')) return |
225 | 227 | ||
226 | const videoPlaylist = res.locals.videoPlaylist | 228 | const videoPlaylist = getPlaylist(res) |
227 | 229 | ||
228 | const videoPlaylistElement = await VideoPlaylistElementModel.loadById(req.params.playlistElementId) | 230 | const videoPlaylistElement = await VideoPlaylistElementModel.loadById(req.params.playlistElementId) |
229 | if (!videoPlaylistElement) { | 231 | if (!videoPlaylistElement) { |
@@ -265,7 +267,7 @@ const videoPlaylistElementAPGetValidator = [ | |||
265 | return res.status(403).end() | 267 | return res.status(403).end() |
266 | } | 268 | } |
267 | 269 | ||
268 | res.locals.videoPlaylistElement = videoPlaylistElement | 270 | res.locals.videoPlaylistElementAP = videoPlaylistElement |
269 | 271 | ||
270 | return next() | 272 | return next() |
271 | } | 273 | } |
@@ -289,7 +291,7 @@ const videoPlaylistsReorderVideosValidator = [ | |||
289 | 291 | ||
290 | if (!await doesVideoPlaylistExist(req.params.playlistId, res, 'all')) return | 292 | if (!await doesVideoPlaylistExist(req.params.playlistId, res, 'all')) return |
291 | 293 | ||
292 | const videoPlaylist = res.locals.videoPlaylist | 294 | const videoPlaylist = getPlaylist(res) |
293 | if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, videoPlaylist, UserRight.UPDATE_ANY_VIDEO_PLAYLIST, res)) return | 295 | if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, videoPlaylist, UserRight.UPDATE_ANY_VIDEO_PLAYLIST, res)) return |
294 | 296 | ||
295 | const nextPosition = await VideoPlaylistElementModel.getNextPositionOf(videoPlaylist.id) | 297 | const nextPosition = await VideoPlaylistElementModel.getNextPositionOf(videoPlaylist.id) |
@@ -388,7 +390,7 @@ function getCommonPlaylistEditAttributes () { | |||
388 | ] as (ValidationChain | express.Handler)[] | 390 | ] as (ValidationChain | express.Handler)[] |
389 | } | 391 | } |
390 | 392 | ||
391 | function checkUserCanManageVideoPlaylist (user: UserModel, videoPlaylist: VideoPlaylistModel, right: UserRight, res: express.Response) { | 393 | function checkUserCanManageVideoPlaylist (user: MUserAccountId, videoPlaylist: MVideoPlaylist, right: UserRight, res: express.Response) { |
392 | if (videoPlaylist.isOwned() === false) { | 394 | if (videoPlaylist.isOwned() === false) { |
393 | res.status(403) | 395 | res.status(403) |
394 | .json({ error: 'Cannot manage video playlist of another server.' }) | 396 | .json({ error: 'Cannot manage video playlist of another server.' }) |
@@ -410,3 +412,7 @@ function checkUserCanManageVideoPlaylist (user: UserModel, videoPlaylist: VideoP | |||
410 | 412 | ||
411 | return true | 413 | return true |
412 | } | 414 | } |
415 | |||
416 | function getPlaylist (res: express.Response) { | ||
417 | return res.locals.videoPlaylistFull || res.locals.videoPlaylistSummary | ||
418 | } | ||
diff --git a/server/middlewares/validators/videos/video-shares.ts b/server/middlewares/validators/videos/video-shares.ts index ace62be5c..20fc96243 100644 --- a/server/middlewares/validators/videos/video-shares.ts +++ b/server/middlewares/validators/videos/video-shares.ts | |||
@@ -16,7 +16,7 @@ const videosShareValidator = [ | |||
16 | if (areValidationErrors(req, res)) return | 16 | if (areValidationErrors(req, res)) return |
17 | if (!await doesVideoExist(req.params.id, res)) return | 17 | if (!await doesVideoExist(req.params.id, res)) return |
18 | 18 | ||
19 | const video = res.locals.video | 19 | const video = res.locals.videoAll |
20 | 20 | ||
21 | const share = await VideoShareModel.load(req.params.actorId, video.id) | 21 | const share = await VideoShareModel.load(req.params.actorId, video.id) |
22 | if (!share) { | 22 | if (!share) { |
diff --git a/server/middlewares/validators/videos/videos.ts b/server/middlewares/validators/videos/videos.ts index af06f3c62..1449903b7 100644 --- a/server/middlewares/validators/videos/videos.ts +++ b/server/middlewares/validators/videos/videos.ts | |||
@@ -37,13 +37,14 @@ import { VideoModel } from '../../../models/video/video' | |||
37 | import { checkUserCanTerminateOwnershipChange, doesChangeVideoOwnershipExist } from '../../../helpers/custom-validators/video-ownership' | 37 | import { checkUserCanTerminateOwnershipChange, doesChangeVideoOwnershipExist } from '../../../helpers/custom-validators/video-ownership' |
38 | import { VideoChangeOwnershipAccept } from '../../../../shared/models/videos/video-change-ownership-accept.model' | 38 | import { VideoChangeOwnershipAccept } from '../../../../shared/models/videos/video-change-ownership-accept.model' |
39 | import { AccountModel } from '../../../models/account/account' | 39 | import { AccountModel } from '../../../models/account/account' |
40 | import { VideoFetchType } from '../../../helpers/video' | ||
41 | import { isNSFWQueryValid, isNumberArray, isStringArray } from '../../../helpers/custom-validators/search' | 40 | import { isNSFWQueryValid, isNumberArray, isStringArray } from '../../../helpers/custom-validators/search' |
42 | import { getServerActor } from '../../../helpers/utils' | 41 | import { getServerActor } from '../../../helpers/utils' |
43 | import { CONFIG } from '../../../initializers/config' | 42 | import { CONFIG } from '../../../initializers/config' |
44 | import { isLocalVideoAccepted } from '../../../lib/moderation' | 43 | import { isLocalVideoAccepted } from '../../../lib/moderation' |
45 | import { Hooks } from '../../../lib/plugins/hooks' | 44 | import { Hooks } from '../../../lib/plugins/hooks' |
46 | import { checkUserCanManageVideo, doesVideoChannelOfAccountExist, doesVideoExist } from '../../../helpers/middlewares' | 45 | import { checkUserCanManageVideo, doesVideoChannelOfAccountExist, doesVideoExist } from '../../../helpers/middlewares' |
46 | import { MVideoFullLight } from '@server/typings/models' | ||
47 | import { getVideoWithAttributes } from '../../../helpers/video' | ||
47 | 48 | ||
48 | const videosAddValidator = getCommonVideoEditAttributes().concat([ | 49 | const videosAddValidator = getCommonVideoEditAttributes().concat([ |
49 | body('videofile') | 50 | body('videofile') |
@@ -113,7 +114,7 @@ const videosUpdateValidator = getCommonVideoEditAttributes().concat([ | |||
113 | 114 | ||
114 | // Check if the user who did the request is able to update the video | 115 | // Check if the user who did the request is able to update the video |
115 | const user = res.locals.oauth.token.User | 116 | const user = res.locals.oauth.token.User |
116 | if (!checkUserCanManageVideo(user, res.locals.video, UserRight.UPDATE_ANY_VIDEO, res)) return cleanUpReqFiles(req) | 117 | if (!checkUserCanManageVideo(user, res.locals.videoAll, UserRight.UPDATE_ANY_VIDEO, res)) return cleanUpReqFiles(req) |
117 | 118 | ||
118 | if (req.body.channelId && !await doesVideoChannelOfAccountExist(req.body.channelId, user, res)) return cleanUpReqFiles(req) | 119 | if (req.body.channelId && !await doesVideoChannelOfAccountExist(req.body.channelId, user, res)) return cleanUpReqFiles(req) |
119 | 120 | ||
@@ -122,7 +123,7 @@ const videosUpdateValidator = getCommonVideoEditAttributes().concat([ | |||
122 | ]) | 123 | ]) |
123 | 124 | ||
124 | async function checkVideoFollowConstraints (req: express.Request, res: express.Response, next: express.NextFunction) { | 125 | async function checkVideoFollowConstraints (req: express.Request, res: express.Response, next: express.NextFunction) { |
125 | const video = res.locals.video | 126 | const video = getVideoWithAttributes(res) |
126 | 127 | ||
127 | // Anybody can watch local videos | 128 | // Anybody can watch local videos |
128 | if (video.isOwned() === true) return next() | 129 | if (video.isOwned() === true) return next() |
@@ -146,7 +147,7 @@ async function checkVideoFollowConstraints (req: express.Request, res: express.R | |||
146 | }) | 147 | }) |
147 | } | 148 | } |
148 | 149 | ||
149 | const videosCustomGetValidator = (fetchType: VideoFetchType) => { | 150 | const videosCustomGetValidator = (fetchType: 'all' | 'only-video' | 'only-video-with-rights') => { |
150 | return [ | 151 | return [ |
151 | param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), | 152 | param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), |
152 | 153 | ||
@@ -156,10 +157,11 @@ const videosCustomGetValidator = (fetchType: VideoFetchType) => { | |||
156 | if (areValidationErrors(req, res)) return | 157 | if (areValidationErrors(req, res)) return |
157 | if (!await doesVideoExist(req.params.id, res, fetchType)) return | 158 | if (!await doesVideoExist(req.params.id, res, fetchType)) return |
158 | 159 | ||
159 | const video = res.locals.video | 160 | const video = getVideoWithAttributes(res) |
161 | const videoAll = video as MVideoFullLight | ||
160 | 162 | ||
161 | // Video private or blacklisted | 163 | // Video private or blacklisted |
162 | if (video.privacy === VideoPrivacy.PRIVATE || video.VideoBlacklist) { | 164 | if (video.privacy === VideoPrivacy.PRIVATE || videoAll.VideoBlacklist) { |
163 | await authenticatePromiseIfNeeded(req, res) | 165 | await authenticatePromiseIfNeeded(req, res) |
164 | 166 | ||
165 | const user = res.locals.oauth ? res.locals.oauth.token.User : null | 167 | const user = res.locals.oauth ? res.locals.oauth.token.User : null |
@@ -167,7 +169,7 @@ const videosCustomGetValidator = (fetchType: VideoFetchType) => { | |||
167 | // Only the owner or a user that have blacklist rights can see the video | 169 | // Only the owner or a user that have blacklist rights can see the video |
168 | if ( | 170 | if ( |
169 | !user || | 171 | !user || |
170 | (video.VideoChannel.Account.userId !== user.id && !user.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST)) | 172 | (videoAll.VideoChannel && videoAll.VideoChannel.Account.userId !== user.id && !user.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST)) |
171 | ) { | 173 | ) { |
172 | return res.status(403) | 174 | return res.status(403) |
173 | .json({ error: 'Cannot get this private or blacklisted video.' }) | 175 | .json({ error: 'Cannot get this private or blacklisted video.' }) |
@@ -202,7 +204,7 @@ const videosRemoveValidator = [ | |||
202 | if (!await doesVideoExist(req.params.id, res)) return | 204 | if (!await doesVideoExist(req.params.id, res)) return |
203 | 205 | ||
204 | // Check if the user who did the request is able to delete the video | 206 | // Check if the user who did the request is able to delete the video |
205 | if (!checkUserCanManageVideo(res.locals.oauth.token.User, res.locals.video, UserRight.REMOVE_ANY_VIDEO, res)) return | 207 | if (!checkUserCanManageVideo(res.locals.oauth.token.User, res.locals.videoAll, UserRight.REMOVE_ANY_VIDEO, res)) return |
206 | 208 | ||
207 | return next() | 209 | return next() |
208 | } | 210 | } |
@@ -218,7 +220,7 @@ const videosChangeOwnershipValidator = [ | |||
218 | if (!await doesVideoExist(req.params.videoId, res)) return | 220 | if (!await doesVideoExist(req.params.videoId, res)) return |
219 | 221 | ||
220 | // Check if the user who did the request is able to change the ownership of the video | 222 | // Check if the user who did the request is able to change the ownership of the video |
221 | if (!checkUserCanManageVideo(res.locals.oauth.token.User, res.locals.video, UserRight.CHANGE_VIDEO_OWNERSHIP, res)) return | 223 | if (!checkUserCanManageVideo(res.locals.oauth.token.User, res.locals.videoAll, UserRight.CHANGE_VIDEO_OWNERSHIP, res)) return |
222 | 224 | ||
223 | const nextOwner = await AccountModel.loadLocalByName(req.body.username) | 225 | const nextOwner = await AccountModel.loadLocalByName(req.body.username) |
224 | if (!nextOwner) { | 226 | if (!nextOwner) { |
diff --git a/server/middlewares/validators/webfinger.ts b/server/middlewares/validators/webfinger.ts index d7cfe17f0..d50e6527f 100644 --- a/server/middlewares/validators/webfinger.ts +++ b/server/middlewares/validators/webfinger.ts | |||
@@ -18,6 +18,7 @@ const webfingerValidator = [ | |||
18 | const nameWithHost = getHostWithPort(req.query.resource.substr(5)) | 18 | const nameWithHost = getHostWithPort(req.query.resource.substr(5)) |
19 | const [ name ] = nameWithHost.split('@') | 19 | const [ name ] = nameWithHost.split('@') |
20 | 20 | ||
21 | // FIXME: we don't need the full actor | ||
21 | const actor = await ActorModel.loadLocalByName(name) | 22 | const actor = await ActorModel.loadLocalByName(name) |
22 | if (!actor) { | 23 | if (!actor) { |
23 | return res.status(404) | 24 | return res.status(404) |
@@ -25,7 +26,7 @@ const webfingerValidator = [ | |||
25 | .end() | 26 | .end() |
26 | } | 27 | } |
27 | 28 | ||
28 | res.locals.actor = actor | 29 | res.locals.actorFull = actor |
29 | return next() | 30 | return next() |
30 | } | 31 | } |
31 | ] | 32 | ] |
diff --git a/server/models/account/account-blocklist.ts b/server/models/account/account-blocklist.ts index d5746ad76..8bcaca828 100644 --- a/server/models/account/account-blocklist.ts +++ b/server/models/account/account-blocklist.ts | |||
@@ -3,6 +3,8 @@ import { AccountModel } from './account' | |||
3 | import { getSort } from '../utils' | 3 | import { getSort } from '../utils' |
4 | import { AccountBlock } from '../../../shared/models/blocklist' | 4 | import { AccountBlock } from '../../../shared/models/blocklist' |
5 | import { Op } from 'sequelize' | 5 | import { Op } from 'sequelize' |
6 | import * as Bluebird from 'bluebird' | ||
7 | import { MAccountBlocklist, MAccountBlocklistAccounts, MAccountBlocklistFormattable } from '@server/typings/models' | ||
6 | 8 | ||
7 | enum ScopeNames { | 9 | enum ScopeNames { |
8 | WITH_ACCOUNTS = 'WITH_ACCOUNTS' | 10 | WITH_ACCOUNTS = 'WITH_ACCOUNTS' |
@@ -103,7 +105,7 @@ export class AccountBlocklistModel extends Model<AccountBlocklistModel> { | |||
103 | }) | 105 | }) |
104 | } | 106 | } |
105 | 107 | ||
106 | static loadByAccountAndTarget (accountId: number, targetAccountId: number) { | 108 | static loadByAccountAndTarget (accountId: number, targetAccountId: number): Bluebird<MAccountBlocklist> { |
107 | const query = { | 109 | const query = { |
108 | where: { | 110 | where: { |
109 | accountId, | 111 | accountId, |
@@ -126,13 +128,13 @@ export class AccountBlocklistModel extends Model<AccountBlocklistModel> { | |||
126 | 128 | ||
127 | return AccountBlocklistModel | 129 | return AccountBlocklistModel |
128 | .scope([ ScopeNames.WITH_ACCOUNTS ]) | 130 | .scope([ ScopeNames.WITH_ACCOUNTS ]) |
129 | .findAndCountAll(query) | 131 | .findAndCountAll<MAccountBlocklistAccounts>(query) |
130 | .then(({ rows, count }) => { | 132 | .then(({ rows, count }) => { |
131 | return { total: count, data: rows } | 133 | return { total: count, data: rows } |
132 | }) | 134 | }) |
133 | } | 135 | } |
134 | 136 | ||
135 | toFormattedJSON (): AccountBlock { | 137 | toFormattedJSON (this: MAccountBlocklistFormattable): AccountBlock { |
136 | return { | 138 | return { |
137 | byAccount: this.ByAccount.toFormattedJSON(), | 139 | byAccount: this.ByAccount.toFormattedJSON(), |
138 | blockedAccount: this.BlockedAccount.toFormattedJSON(), | 140 | blockedAccount: this.BlockedAccount.toFormattedJSON(), |
diff --git a/server/models/account/account-video-rate.ts b/server/models/account/account-video-rate.ts index 4bd8114cf..a6edbeee8 100644 --- a/server/models/account/account-video-rate.ts +++ b/server/models/account/account-video-rate.ts | |||
@@ -10,6 +10,13 @@ import { buildLocalAccountIdsIn, getSort, throwIfNotValid } from '../utils' | |||
10 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' | 10 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' |
11 | import { AccountVideoRate } from '../../../shared' | 11 | import { AccountVideoRate } from '../../../shared' |
12 | import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from '../video/video-channel' | 12 | import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from '../video/video-channel' |
13 | import * as Bluebird from 'bluebird' | ||
14 | import { | ||
15 | MAccountVideoRate, | ||
16 | MAccountVideoRateAccountUrl, | ||
17 | MAccountVideoRateAccountVideo, | ||
18 | MAccountVideoRateFormattable | ||
19 | } from '@server/typings/models/video/video-rate' | ||
13 | 20 | ||
14 | /* | 21 | /* |
15 | Account rates per video. | 22 | Account rates per video. |
@@ -77,7 +84,7 @@ export class AccountVideoRateModel extends Model<AccountVideoRateModel> { | |||
77 | }) | 84 | }) |
78 | Account: AccountModel | 85 | Account: AccountModel |
79 | 86 | ||
80 | static load (accountId: number, videoId: number, transaction?: Transaction) { | 87 | static load (accountId: number, videoId: number, transaction?: Transaction): Bluebird<MAccountVideoRate> { |
81 | const options: FindOptions = { | 88 | const options: FindOptions = { |
82 | where: { | 89 | where: { |
83 | accountId, | 90 | accountId, |
@@ -89,7 +96,7 @@ export class AccountVideoRateModel extends Model<AccountVideoRateModel> { | |||
89 | return AccountVideoRateModel.findOne(options) | 96 | return AccountVideoRateModel.findOne(options) |
90 | } | 97 | } |
91 | 98 | ||
92 | static loadByAccountAndVideoOrUrl (accountId: number, videoId: number, url: string, transaction?: Transaction) { | 99 | static loadByAccountAndVideoOrUrl (accountId: number, videoId: number, url: string, t?: Transaction): Bluebird<MAccountVideoRate> { |
93 | const options: FindOptions = { | 100 | const options: FindOptions = { |
94 | where: { | 101 | where: { |
95 | [ Op.or]: [ | 102 | [ Op.or]: [ |
@@ -103,7 +110,7 @@ export class AccountVideoRateModel extends Model<AccountVideoRateModel> { | |||
103 | ] | 110 | ] |
104 | } | 111 | } |
105 | } | 112 | } |
106 | if (transaction) options.transaction = transaction | 113 | if (t) options.transaction = t |
107 | 114 | ||
108 | return AccountVideoRateModel.findOne(options) | 115 | return AccountVideoRateModel.findOne(options) |
109 | } | 116 | } |
@@ -140,7 +147,12 @@ export class AccountVideoRateModel extends Model<AccountVideoRateModel> { | |||
140 | return AccountVideoRateModel.findAndCountAll(query) | 147 | return AccountVideoRateModel.findAndCountAll(query) |
141 | } | 148 | } |
142 | 149 | ||
143 | static loadLocalAndPopulateVideo (rateType: VideoRateType, accountName: string, videoId: number, transaction?: Transaction) { | 150 | static loadLocalAndPopulateVideo ( |
151 | rateType: VideoRateType, | ||
152 | accountName: string, | ||
153 | videoId: number, | ||
154 | t?: Transaction | ||
155 | ): Bluebird<MAccountVideoRateAccountVideo> { | ||
144 | const options: FindOptions = { | 156 | const options: FindOptions = { |
145 | where: { | 157 | where: { |
146 | videoId, | 158 | videoId, |
@@ -152,7 +164,7 @@ export class AccountVideoRateModel extends Model<AccountVideoRateModel> { | |||
152 | required: true, | 164 | required: true, |
153 | include: [ | 165 | include: [ |
154 | { | 166 | { |
155 | attributes: [ 'id', 'url', 'preferredUsername' ], | 167 | attributes: [ 'id', 'url', 'followersUrl', 'preferredUsername' ], |
156 | model: ActorModel.unscoped(), | 168 | model: ActorModel.unscoped(), |
157 | required: true, | 169 | required: true, |
158 | where: { | 170 | where: { |
@@ -167,7 +179,7 @@ export class AccountVideoRateModel extends Model<AccountVideoRateModel> { | |||
167 | } | 179 | } |
168 | ] | 180 | ] |
169 | } | 181 | } |
170 | if (transaction) options.transaction = transaction | 182 | if (t) options.transaction = t |
171 | 183 | ||
172 | return AccountVideoRateModel.findOne(options) | 184 | return AccountVideoRateModel.findOne(options) |
173 | } | 185 | } |
@@ -208,7 +220,7 @@ export class AccountVideoRateModel extends Model<AccountVideoRateModel> { | |||
208 | ] | 220 | ] |
209 | } | 221 | } |
210 | 222 | ||
211 | return AccountVideoRateModel.findAndCountAll(query) | 223 | return AccountVideoRateModel.findAndCountAll<MAccountVideoRateAccountUrl>(query) |
212 | } | 224 | } |
213 | 225 | ||
214 | static cleanOldRatesOf (videoId: number, type: VideoRateType, beforeUpdatedAt: Date) { | 226 | static cleanOldRatesOf (videoId: number, type: VideoRateType, beforeUpdatedAt: Date) { |
@@ -241,7 +253,7 @@ export class AccountVideoRateModel extends Model<AccountVideoRateModel> { | |||
241 | }) | 253 | }) |
242 | } | 254 | } |
243 | 255 | ||
244 | toFormattedJSON (): AccountVideoRate { | 256 | toFormattedJSON (this: MAccountVideoRateFormattable): AccountVideoRate { |
245 | return { | 257 | return { |
246 | video: this.Video.toFormattedJSON(), | 258 | video: this.Video.toFormattedJSON(), |
247 | rating: this.type | 259 | rating: this.type |
diff --git a/server/models/account/account.ts b/server/models/account/account.ts index 4dc412301..ba1094536 100644 --- a/server/models/account/account.ts +++ b/server/models/account/account.ts | |||
@@ -3,7 +3,8 @@ import { | |||
3 | BeforeDestroy, | 3 | BeforeDestroy, |
4 | BelongsTo, | 4 | BelongsTo, |
5 | Column, | 5 | Column, |
6 | CreatedAt, DataType, | 6 | CreatedAt, |
7 | DataType, | ||
7 | Default, | 8 | Default, |
8 | DefaultScope, | 9 | DefaultScope, |
9 | ForeignKey, | 10 | ForeignKey, |
@@ -31,6 +32,8 @@ import { FindOptions, IncludeOptions, Op, Transaction, WhereOptions } from 'sequ | |||
31 | import { AccountBlocklistModel } from './account-blocklist' | 32 | import { AccountBlocklistModel } from './account-blocklist' |
32 | import { ServerBlocklistModel } from '../server/server-blocklist' | 33 | import { ServerBlocklistModel } from '../server/server-blocklist' |
33 | import { ActorFollowModel } from '../activitypub/actor-follow' | 34 | import { ActorFollowModel } from '../activitypub/actor-follow' |
35 | import { MAccountActor, MAccountDefault, MAccountSummaryFormattable, MAccountFormattable, MAccountAP } from '../../typings/models' | ||
36 | import * as Bluebird from 'bluebird' | ||
34 | 37 | ||
35 | export enum ScopeNames { | 38 | export enum ScopeNames { |
36 | SUMMARY = 'SUMMARY' | 39 | SUMMARY = 'SUMMARY' |
@@ -229,11 +232,11 @@ export class AccountModel extends Model<AccountModel> { | |||
229 | return undefined | 232 | return undefined |
230 | } | 233 | } |
231 | 234 | ||
232 | static load (id: number, transaction?: Transaction) { | 235 | static load (id: number, transaction?: Transaction): Bluebird<MAccountDefault> { |
233 | return AccountModel.findByPk(id, { transaction }) | 236 | return AccountModel.findByPk(id, { transaction }) |
234 | } | 237 | } |
235 | 238 | ||
236 | static loadByNameWithHost (nameWithHost: string) { | 239 | static loadByNameWithHost (nameWithHost: string): Bluebird<MAccountDefault> { |
237 | const [ accountName, host ] = nameWithHost.split('@') | 240 | const [ accountName, host ] = nameWithHost.split('@') |
238 | 241 | ||
239 | if (!host || host === WEBSERVER.HOST) return AccountModel.loadLocalByName(accountName) | 242 | if (!host || host === WEBSERVER.HOST) return AccountModel.loadLocalByName(accountName) |
@@ -241,7 +244,7 @@ export class AccountModel extends Model<AccountModel> { | |||
241 | return AccountModel.loadByNameAndHost(accountName, host) | 244 | return AccountModel.loadByNameAndHost(accountName, host) |
242 | } | 245 | } |
243 | 246 | ||
244 | static loadLocalByName (name: string) { | 247 | static loadLocalByName (name: string): Bluebird<MAccountDefault> { |
245 | const query = { | 248 | const query = { |
246 | where: { | 249 | where: { |
247 | [ Op.or ]: [ | 250 | [ Op.or ]: [ |
@@ -271,7 +274,7 @@ export class AccountModel extends Model<AccountModel> { | |||
271 | return AccountModel.findOne(query) | 274 | return AccountModel.findOne(query) |
272 | } | 275 | } |
273 | 276 | ||
274 | static loadByNameAndHost (name: string, host: string) { | 277 | static loadByNameAndHost (name: string, host: string): Bluebird<MAccountDefault> { |
275 | const query = { | 278 | const query = { |
276 | include: [ | 279 | include: [ |
277 | { | 280 | { |
@@ -296,7 +299,7 @@ export class AccountModel extends Model<AccountModel> { | |||
296 | return AccountModel.findOne(query) | 299 | return AccountModel.findOne(query) |
297 | } | 300 | } |
298 | 301 | ||
299 | static loadByUrl (url: string, transaction?: Transaction) { | 302 | static loadByUrl (url: string, transaction?: Transaction): Bluebird<MAccountDefault> { |
300 | const query = { | 303 | const query = { |
301 | include: [ | 304 | include: [ |
302 | { | 305 | { |
@@ -329,7 +332,7 @@ export class AccountModel extends Model<AccountModel> { | |||
329 | }) | 332 | }) |
330 | } | 333 | } |
331 | 334 | ||
332 | static listLocalsForSitemap (sort: string) { | 335 | static listLocalsForSitemap (sort: string): Bluebird<MAccountActor[]> { |
333 | const query = { | 336 | const query = { |
334 | attributes: [ ], | 337 | attributes: [ ], |
335 | offset: 0, | 338 | offset: 0, |
@@ -350,7 +353,7 @@ export class AccountModel extends Model<AccountModel> { | |||
350 | .findAll(query) | 353 | .findAll(query) |
351 | } | 354 | } |
352 | 355 | ||
353 | toFormattedJSON (): Account { | 356 | toFormattedJSON (this: MAccountFormattable): Account { |
354 | const actor = this.Actor.toFormattedJSON() | 357 | const actor = this.Actor.toFormattedJSON() |
355 | const account = { | 358 | const account = { |
356 | id: this.id, | 359 | id: this.id, |
@@ -364,8 +367,8 @@ export class AccountModel extends Model<AccountModel> { | |||
364 | return Object.assign(actor, account) | 367 | return Object.assign(actor, account) |
365 | } | 368 | } |
366 | 369 | ||
367 | toFormattedSummaryJSON (): AccountSummary { | 370 | toFormattedSummaryJSON (this: MAccountSummaryFormattable): AccountSummary { |
368 | const actor = this.Actor.toFormattedJSON() | 371 | const actor = this.Actor.toFormattedSummaryJSON() |
369 | 372 | ||
370 | return { | 373 | return { |
371 | id: this.id, | 374 | id: this.id, |
@@ -377,8 +380,8 @@ export class AccountModel extends Model<AccountModel> { | |||
377 | } | 380 | } |
378 | } | 381 | } |
379 | 382 | ||
380 | toActivityPubObject () { | 383 | toActivityPubObject (this: MAccountAP) { |
381 | const obj = this.Actor.toActivityPubObject(this.name, 'Account') | 384 | const obj = this.Actor.toActivityPubObject(this.name) |
382 | 385 | ||
383 | return Object.assign(obj, { | 386 | return Object.assign(obj, { |
384 | summary: this.description | 387 | summary: this.description |
diff --git a/server/models/account/user-notification-setting.ts b/server/models/account/user-notification-setting.ts index c2fbc6d23..dc69a17fd 100644 --- a/server/models/account/user-notification-setting.ts +++ b/server/models/account/user-notification-setting.ts | |||
@@ -17,6 +17,7 @@ import { UserModel } from './user' | |||
17 | import { isUserNotificationSettingValid } from '../../helpers/custom-validators/user-notifications' | 17 | import { isUserNotificationSettingValid } from '../../helpers/custom-validators/user-notifications' |
18 | import { UserNotificationSetting, UserNotificationSettingValue } from '../../../shared/models/users/user-notification-setting.model' | 18 | import { UserNotificationSetting, UserNotificationSettingValue } from '../../../shared/models/users/user-notification-setting.model' |
19 | import { clearCacheByUserId } from '../../lib/oauth-model' | 19 | import { clearCacheByUserId } from '../../lib/oauth-model' |
20 | import { MNotificationSettingFormattable } from '@server/typings/models' | ||
20 | 21 | ||
21 | @Table({ | 22 | @Table({ |
22 | tableName: 'userNotificationSetting', | 23 | tableName: 'userNotificationSetting', |
@@ -113,6 +114,15 @@ export class UserNotificationSettingModel extends Model<UserNotificationSettingM | |||
113 | @AllowNull(false) | 114 | @AllowNull(false) |
114 | @Default(null) | 115 | @Default(null) |
115 | @Is( | 116 | @Is( |
117 | 'UserNotificationSettingNewInstanceFollower', | ||
118 | value => throwIfNotValid(value, isUserNotificationSettingValid, 'autoInstanceFollowing') | ||
119 | ) | ||
120 | @Column | ||
121 | autoInstanceFollowing: UserNotificationSettingValue | ||
122 | |||
123 | @AllowNull(false) | ||
124 | @Default(null) | ||
125 | @Is( | ||
116 | 'UserNotificationSettingNewFollow', | 126 | 'UserNotificationSettingNewFollow', |
117 | value => throwIfNotValid(value, isUserNotificationSettingValid, 'newFollow') | 127 | value => throwIfNotValid(value, isUserNotificationSettingValid, 'newFollow') |
118 | ) | 128 | ) |
@@ -152,7 +162,7 @@ export class UserNotificationSettingModel extends Model<UserNotificationSettingM | |||
152 | return clearCacheByUserId(instance.userId) | 162 | return clearCacheByUserId(instance.userId) |
153 | } | 163 | } |
154 | 164 | ||
155 | toFormattedJSON (): UserNotificationSetting { | 165 | toFormattedJSON (this: MNotificationSettingFormattable): UserNotificationSetting { |
156 | return { | 166 | return { |
157 | newCommentOnMyVideo: this.newCommentOnMyVideo, | 167 | newCommentOnMyVideo: this.newCommentOnMyVideo, |
158 | newVideoFromSubscription: this.newVideoFromSubscription, | 168 | newVideoFromSubscription: this.newVideoFromSubscription, |
@@ -164,7 +174,8 @@ export class UserNotificationSettingModel extends Model<UserNotificationSettingM | |||
164 | newUserRegistration: this.newUserRegistration, | 174 | newUserRegistration: this.newUserRegistration, |
165 | commentMention: this.commentMention, | 175 | commentMention: this.commentMention, |
166 | newFollow: this.newFollow, | 176 | newFollow: this.newFollow, |
167 | newInstanceFollower: this.newInstanceFollower | 177 | newInstanceFollower: this.newInstanceFollower, |
178 | autoInstanceFollowing: this.autoInstanceFollowing | ||
168 | } | 179 | } |
169 | } | 180 | } |
170 | } | 181 | } |
diff --git a/server/models/account/user-notification.ts b/server/models/account/user-notification.ts index f38cd7e78..ccb81b891 100644 --- a/server/models/account/user-notification.ts +++ b/server/models/account/user-notification.ts | |||
@@ -16,6 +16,7 @@ import { ActorModel } from '../activitypub/actor' | |||
16 | import { ActorFollowModel } from '../activitypub/actor-follow' | 16 | import { ActorFollowModel } from '../activitypub/actor-follow' |
17 | import { AvatarModel } from '../avatar/avatar' | 17 | import { AvatarModel } from '../avatar/avatar' |
18 | import { ServerModel } from '../server/server' | 18 | import { ServerModel } from '../server/server' |
19 | import { UserNotificationIncludes, UserNotificationModelForApi } from '@server/typings/models/user' | ||
19 | 20 | ||
20 | enum ScopeNames { | 21 | enum ScopeNames { |
21 | WITH_ALL = 'WITH_ALL' | 22 | WITH_ALL = 'WITH_ALL' |
@@ -134,13 +135,18 @@ function buildAccountInclude (required: boolean, withActor = false) { | |||
134 | ] | 135 | ] |
135 | }, | 136 | }, |
136 | { | 137 | { |
137 | attributes: [ 'preferredUsername' ], | 138 | attributes: [ 'preferredUsername', 'type' ], |
138 | model: ActorModel.unscoped(), | 139 | model: ActorModel.unscoped(), |
139 | required: true, | 140 | required: true, |
140 | as: 'ActorFollowing', | 141 | as: 'ActorFollowing', |
141 | include: [ | 142 | include: [ |
142 | buildChannelInclude(false), | 143 | buildChannelInclude(false), |
143 | buildAccountInclude(false) | 144 | buildAccountInclude(false), |
145 | { | ||
146 | attributes: [ 'host' ], | ||
147 | model: ServerModel.unscoped(), | ||
148 | required: false | ||
149 | } | ||
144 | ] | 150 | ] |
145 | } | 151 | } |
146 | ] | 152 | ] |
@@ -371,7 +377,7 @@ export class UserNotificationModel extends Model<UserNotificationModel> { | |||
371 | return UserNotificationModel.update({ read: true }, query) | 377 | return UserNotificationModel.update({ read: true }, query) |
372 | } | 378 | } |
373 | 379 | ||
374 | toFormattedJSON (): UserNotification { | 380 | toFormattedJSON (this: UserNotificationModelForApi): UserNotification { |
375 | const video = this.Video | 381 | const video = this.Video |
376 | ? Object.assign(this.formatVideo(this.Video),{ channel: this.formatActor(this.Video.VideoChannel) }) | 382 | ? Object.assign(this.formatVideo(this.Video),{ channel: this.formatActor(this.Video.VideoChannel) }) |
377 | : undefined | 383 | : undefined |
@@ -403,6 +409,11 @@ export class UserNotificationModel extends Model<UserNotificationModel> { | |||
403 | 409 | ||
404 | const account = this.Account ? this.formatActor(this.Account) : undefined | 410 | const account = this.Account ? this.formatActor(this.Account) : undefined |
405 | 411 | ||
412 | const actorFollowingType = { | ||
413 | Application: 'instance' as 'instance', | ||
414 | Group: 'channel' as 'channel', | ||
415 | Person: 'account' as 'account' | ||
416 | } | ||
406 | const actorFollow = this.ActorFollow ? { | 417 | const actorFollow = this.ActorFollow ? { |
407 | id: this.ActorFollow.id, | 418 | id: this.ActorFollow.id, |
408 | state: this.ActorFollow.state, | 419 | state: this.ActorFollow.state, |
@@ -414,9 +425,10 @@ export class UserNotificationModel extends Model<UserNotificationModel> { | |||
414 | host: this.ActorFollow.ActorFollower.getHost() | 425 | host: this.ActorFollow.ActorFollower.getHost() |
415 | }, | 426 | }, |
416 | following: { | 427 | following: { |
417 | type: this.ActorFollow.ActorFollowing.VideoChannel ? 'channel' as 'channel' : 'account' as 'account', | 428 | type: actorFollowingType[this.ActorFollow.ActorFollowing.type], |
418 | displayName: (this.ActorFollow.ActorFollowing.VideoChannel || this.ActorFollow.ActorFollowing.Account).getDisplayName(), | 429 | displayName: (this.ActorFollow.ActorFollowing.VideoChannel || this.ActorFollow.ActorFollowing.Account).getDisplayName(), |
419 | name: this.ActorFollow.ActorFollowing.preferredUsername | 430 | name: this.ActorFollow.ActorFollowing.preferredUsername, |
431 | host: this.ActorFollow.ActorFollowing.getHost() | ||
420 | } | 432 | } |
421 | } : undefined | 433 | } : undefined |
422 | 434 | ||
@@ -436,7 +448,7 @@ export class UserNotificationModel extends Model<UserNotificationModel> { | |||
436 | } | 448 | } |
437 | } | 449 | } |
438 | 450 | ||
439 | private formatVideo (video: VideoModel) { | 451 | formatVideo (this: UserNotificationModelForApi, video: UserNotificationIncludes.VideoInclude) { |
440 | return { | 452 | return { |
441 | id: video.id, | 453 | id: video.id, |
442 | uuid: video.uuid, | 454 | uuid: video.uuid, |
@@ -444,7 +456,10 @@ export class UserNotificationModel extends Model<UserNotificationModel> { | |||
444 | } | 456 | } |
445 | } | 457 | } |
446 | 458 | ||
447 | private formatActor (accountOrChannel: AccountModel | VideoChannelModel) { | 459 | formatActor ( |
460 | this: UserNotificationModelForApi, | ||
461 | accountOrChannel: UserNotificationIncludes.AccountIncludeActor | UserNotificationIncludes.VideoChannelIncludeActor | ||
462 | ) { | ||
448 | const avatar = accountOrChannel.Actor.Avatar | 463 | const avatar = accountOrChannel.Actor.Avatar |
449 | ? { path: accountOrChannel.Actor.Avatar.getStaticPath() } | 464 | ? { path: accountOrChannel.Actor.Avatar.getStaticPath() } |
450 | : undefined | 465 | : undefined |
diff --git a/server/models/account/user-video-history.ts b/server/models/account/user-video-history.ts index a862fc45f..3fe4c8db1 100644 --- a/server/models/account/user-video-history.ts +++ b/server/models/account/user-video-history.ts | |||
@@ -1,7 +1,8 @@ | |||
1 | import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, IsInt, Model, Table, UpdatedAt } from 'sequelize-typescript' | 1 | import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, IsInt, Model, Table, UpdatedAt } from 'sequelize-typescript' |
2 | import { VideoModel } from '../video/video' | 2 | import { VideoModel } from '../video/video' |
3 | import { UserModel } from './user' | 3 | import { UserModel } from './user' |
4 | import { Transaction, Op, DestroyOptions } from 'sequelize' | 4 | import { DestroyOptions, Op, Transaction } from 'sequelize' |
5 | import { MUserAccountId, MUserId } from '@server/typings/models' | ||
5 | 6 | ||
6 | @Table({ | 7 | @Table({ |
7 | tableName: 'userVideoHistory', | 8 | tableName: 'userVideoHistory', |
@@ -54,7 +55,7 @@ export class UserVideoHistoryModel extends Model<UserVideoHistoryModel> { | |||
54 | }) | 55 | }) |
55 | User: UserModel | 56 | User: UserModel |
56 | 57 | ||
57 | static listForApi (user: UserModel, start: number, count: number) { | 58 | static listForApi (user: MUserAccountId, start: number, count: number) { |
58 | return VideoModel.listForApi({ | 59 | return VideoModel.listForApi({ |
59 | start, | 60 | start, |
60 | count, | 61 | count, |
@@ -67,7 +68,7 @@ export class UserVideoHistoryModel extends Model<UserVideoHistoryModel> { | |||
67 | }) | 68 | }) |
68 | } | 69 | } |
69 | 70 | ||
70 | static removeUserHistoryBefore (user: UserModel, beforeDate: string, t: Transaction) { | 71 | static removeUserHistoryBefore (user: MUserId, beforeDate: string, t: Transaction) { |
71 | const query: DestroyOptions = { | 72 | const query: DestroyOptions = { |
72 | where: { | 73 | where: { |
73 | userId: user.id | 74 | userId: user.id |
diff --git a/server/models/account/user.ts b/server/models/account/user.ts index 0041bf577..451e1fd6b 100644 --- a/server/models/account/user.ts +++ b/server/models/account/user.ts | |||
@@ -22,6 +22,7 @@ import { | |||
22 | import { hasUserRight, USER_ROLE_LABELS, UserRight } from '../../../shared' | 22 | import { hasUserRight, USER_ROLE_LABELS, UserRight } from '../../../shared' |
23 | import { User, UserRole } from '../../../shared/models/users' | 23 | import { User, UserRole } from '../../../shared/models/users' |
24 | import { | 24 | import { |
25 | isNoInstanceConfigWarningModal, | ||
25 | isUserAdminFlagsValid, | 26 | isUserAdminFlagsValid, |
26 | isUserAutoPlayVideoValid, | 27 | isUserAutoPlayVideoValid, |
27 | isUserBlockedReasonValid, | 28 | isUserBlockedReasonValid, |
@@ -35,7 +36,8 @@ import { | |||
35 | isUserVideoQuotaDailyValid, | 36 | isUserVideoQuotaDailyValid, |
36 | isUserVideoQuotaValid, | 37 | isUserVideoQuotaValid, |
37 | isUserVideosHistoryEnabledValid, | 38 | isUserVideosHistoryEnabledValid, |
38 | isUserWebTorrentEnabledValid | 39 | isUserWebTorrentEnabledValid, |
40 | isNoWelcomeModal | ||
39 | } from '../../helpers/custom-validators/users' | 41 | } from '../../helpers/custom-validators/users' |
40 | import { comparePassword, cryptPassword } from '../../helpers/peertube-crypto' | 42 | import { comparePassword, cryptPassword } from '../../helpers/peertube-crypto' |
41 | import { OAuthTokenModel } from '../oauth/oauth-token' | 43 | import { OAuthTokenModel } from '../oauth/oauth-token' |
@@ -54,6 +56,14 @@ import { VideoImportModel } from '../video/video-import' | |||
54 | import { UserAdminFlag } from '../../../shared/models/users/user-flag.model' | 56 | import { UserAdminFlag } from '../../../shared/models/users/user-flag.model' |
55 | import { isThemeNameValid } from '../../helpers/custom-validators/plugins' | 57 | import { isThemeNameValid } from '../../helpers/custom-validators/plugins' |
56 | import { getThemeOrDefault } from '../../lib/plugins/theme-utils' | 58 | import { getThemeOrDefault } from '../../lib/plugins/theme-utils' |
59 | import * as Bluebird from 'bluebird' | ||
60 | import { | ||
61 | MUserDefault, | ||
62 | MUserFormattable, | ||
63 | MUserId, | ||
64 | MUserNotifSettingChannelDefault, | ||
65 | MUserWithNotificationSetting | ||
66 | } from '@server/typings/models' | ||
57 | 67 | ||
58 | enum ScopeNames { | 68 | enum ScopeNames { |
59 | WITH_VIDEO_CHANNEL = 'WITH_VIDEO_CHANNEL' | 69 | WITH_VIDEO_CHANNEL = 'WITH_VIDEO_CHANNEL' |
@@ -195,6 +205,24 @@ export class UserModel extends Model<UserModel> { | |||
195 | @Column | 205 | @Column |
196 | theme: string | 206 | theme: string |
197 | 207 | ||
208 | @AllowNull(false) | ||
209 | @Default(false) | ||
210 | @Is( | ||
211 | 'UserNoInstanceConfigWarningModal', | ||
212 | value => throwIfNotValid(value, isNoInstanceConfigWarningModal, 'no instance config warning modal') | ||
213 | ) | ||
214 | @Column | ||
215 | noInstanceConfigWarningModal: boolean | ||
216 | |||
217 | @AllowNull(false) | ||
218 | @Default(false) | ||
219 | @Is( | ||
220 | 'UserNoInstanceConfigWarningModal', | ||
221 | value => throwIfNotValid(value, isNoWelcomeModal, 'no welcome modal') | ||
222 | ) | ||
223 | @Column | ||
224 | noWelcomeModal: boolean | ||
225 | |||
198 | @CreatedAt | 226 | @CreatedAt |
199 | createdAt: Date | 227 | createdAt: Date |
200 | 228 | ||
@@ -303,7 +331,7 @@ export class UserModel extends Model<UserModel> { | |||
303 | }) | 331 | }) |
304 | } | 332 | } |
305 | 333 | ||
306 | static listWithRight (right: UserRight) { | 334 | static listWithRight (right: UserRight): Bluebird<MUserDefault[]> { |
307 | const roles = Object.keys(USER_ROLE_LABELS) | 335 | const roles = Object.keys(USER_ROLE_LABELS) |
308 | .map(k => parseInt(k, 10) as UserRole) | 336 | .map(k => parseInt(k, 10) as UserRole) |
309 | .filter(role => hasUserRight(role, right)) | 337 | .filter(role => hasUserRight(role, right)) |
@@ -319,7 +347,7 @@ export class UserModel extends Model<UserModel> { | |||
319 | return UserModel.findAll(query) | 347 | return UserModel.findAll(query) |
320 | } | 348 | } |
321 | 349 | ||
322 | static listUserSubscribersOf (actorId: number) { | 350 | static listUserSubscribersOf (actorId: number): Bluebird<MUserWithNotificationSetting[]> { |
323 | const query = { | 351 | const query = { |
324 | include: [ | 352 | include: [ |
325 | { | 353 | { |
@@ -358,7 +386,7 @@ export class UserModel extends Model<UserModel> { | |||
358 | return UserModel.unscoped().findAll(query) | 386 | return UserModel.unscoped().findAll(query) |
359 | } | 387 | } |
360 | 388 | ||
361 | static listByUsernames (usernames: string[]) { | 389 | static listByUsernames (usernames: string[]): Bluebird<MUserDefault[]> { |
362 | const query = { | 390 | const query = { |
363 | where: { | 391 | where: { |
364 | username: usernames | 392 | username: usernames |
@@ -368,11 +396,11 @@ export class UserModel extends Model<UserModel> { | |||
368 | return UserModel.findAll(query) | 396 | return UserModel.findAll(query) |
369 | } | 397 | } |
370 | 398 | ||
371 | static loadById (id: number) { | 399 | static loadById (id: number): Bluebird<MUserDefault> { |
372 | return UserModel.findByPk(id) | 400 | return UserModel.findByPk(id) |
373 | } | 401 | } |
374 | 402 | ||
375 | static loadByUsername (username: string) { | 403 | static loadByUsername (username: string): Bluebird<MUserDefault> { |
376 | const query = { | 404 | const query = { |
377 | where: { | 405 | where: { |
378 | username: { [ Op.iLike ]: username } | 406 | username: { [ Op.iLike ]: username } |
@@ -382,7 +410,7 @@ export class UserModel extends Model<UserModel> { | |||
382 | return UserModel.findOne(query) | 410 | return UserModel.findOne(query) |
383 | } | 411 | } |
384 | 412 | ||
385 | static loadByUsernameAndPopulateChannels (username: string) { | 413 | static loadByUsernameAndPopulateChannels (username: string): Bluebird<MUserNotifSettingChannelDefault> { |
386 | const query = { | 414 | const query = { |
387 | where: { | 415 | where: { |
388 | username: { [ Op.iLike ]: username } | 416 | username: { [ Op.iLike ]: username } |
@@ -392,7 +420,7 @@ export class UserModel extends Model<UserModel> { | |||
392 | return UserModel.scope(ScopeNames.WITH_VIDEO_CHANNEL).findOne(query) | 420 | return UserModel.scope(ScopeNames.WITH_VIDEO_CHANNEL).findOne(query) |
393 | } | 421 | } |
394 | 422 | ||
395 | static loadByEmail (email: string) { | 423 | static loadByEmail (email: string): Bluebird<MUserDefault> { |
396 | const query = { | 424 | const query = { |
397 | where: { | 425 | where: { |
398 | 426 | ||
@@ -402,7 +430,7 @@ export class UserModel extends Model<UserModel> { | |||
402 | return UserModel.findOne(query) | 430 | return UserModel.findOne(query) |
403 | } | 431 | } |
404 | 432 | ||
405 | static loadByUsernameOrEmail (username: string, email?: string) { | 433 | static loadByUsernameOrEmail (username: string, email?: string): Bluebird<MUserDefault> { |
406 | if (!email) email = username | 434 | if (!email) email = username |
407 | 435 | ||
408 | const query = { | 436 | const query = { |
@@ -414,7 +442,7 @@ export class UserModel extends Model<UserModel> { | |||
414 | return UserModel.findOne(query) | 442 | return UserModel.findOne(query) |
415 | } | 443 | } |
416 | 444 | ||
417 | static loadByVideoId (videoId: number) { | 445 | static loadByVideoId (videoId: number): Bluebird<MUserDefault> { |
418 | const query = { | 446 | const query = { |
419 | include: [ | 447 | include: [ |
420 | { | 448 | { |
@@ -445,7 +473,7 @@ export class UserModel extends Model<UserModel> { | |||
445 | return UserModel.findOne(query) | 473 | return UserModel.findOne(query) |
446 | } | 474 | } |
447 | 475 | ||
448 | static loadByVideoImportId (videoImportId: number) { | 476 | static loadByVideoImportId (videoImportId: number): Bluebird<MUserDefault> { |
449 | const query = { | 477 | const query = { |
450 | include: [ | 478 | include: [ |
451 | { | 479 | { |
@@ -462,7 +490,7 @@ export class UserModel extends Model<UserModel> { | |||
462 | return UserModel.findOne(query) | 490 | return UserModel.findOne(query) |
463 | } | 491 | } |
464 | 492 | ||
465 | static loadByChannelActorId (videoChannelActorId: number) { | 493 | static loadByChannelActorId (videoChannelActorId: number): Bluebird<MUserDefault> { |
466 | const query = { | 494 | const query = { |
467 | include: [ | 495 | include: [ |
468 | { | 496 | { |
@@ -486,7 +514,7 @@ export class UserModel extends Model<UserModel> { | |||
486 | return UserModel.findOne(query) | 514 | return UserModel.findOne(query) |
487 | } | 515 | } |
488 | 516 | ||
489 | static loadByAccountActorId (accountActorId: number) { | 517 | static loadByAccountActorId (accountActorId: number): Bluebird<MUserDefault> { |
490 | const query = { | 518 | const query = { |
491 | include: [ | 519 | include: [ |
492 | { | 520 | { |
@@ -503,7 +531,7 @@ export class UserModel extends Model<UserModel> { | |||
503 | return UserModel.findOne(query) | 531 | return UserModel.findOne(query) |
504 | } | 532 | } |
505 | 533 | ||
506 | static getOriginalVideoFileTotalFromUser (user: UserModel) { | 534 | static getOriginalVideoFileTotalFromUser (user: MUserId) { |
507 | // Don't use sequelize because we need to use a sub query | 535 | // Don't use sequelize because we need to use a sub query |
508 | const query = UserModel.generateUserQuotaBaseSQL() | 536 | const query = UserModel.generateUserQuotaBaseSQL() |
509 | 537 | ||
@@ -511,7 +539,7 @@ export class UserModel extends Model<UserModel> { | |||
511 | } | 539 | } |
512 | 540 | ||
513 | // Returns cumulative size of all video files uploaded in the last 24 hours. | 541 | // Returns cumulative size of all video files uploaded in the last 24 hours. |
514 | static getOriginalVideoFileTotalDailyFromUser (user: UserModel) { | 542 | static getOriginalVideoFileTotalDailyFromUser (user: MUserId) { |
515 | // Don't use sequelize because we need to use a sub query | 543 | // Don't use sequelize because we need to use a sub query |
516 | const query = UserModel.generateUserQuotaBaseSQL('"video"."createdAt" > now() - interval \'24 hours\'') | 544 | const query = UserModel.generateUserQuotaBaseSQL('"video"."createdAt" > now() - interval \'24 hours\'') |
517 | 545 | ||
@@ -552,38 +580,52 @@ export class UserModel extends Model<UserModel> { | |||
552 | return comparePassword(password, this.password) | 580 | return comparePassword(password, this.password) |
553 | } | 581 | } |
554 | 582 | ||
555 | toFormattedJSON (parameters: { withAdminFlags?: boolean } = {}): User { | 583 | toFormattedJSON (this: MUserFormattable, parameters: { withAdminFlags?: boolean } = {}): User { |
556 | const videoQuotaUsed = this.get('videoQuotaUsed') | 584 | const videoQuotaUsed = this.get('videoQuotaUsed') |
557 | const videoQuotaUsedDaily = this.get('videoQuotaUsedDaily') | 585 | const videoQuotaUsedDaily = this.get('videoQuotaUsedDaily') |
558 | 586 | ||
559 | const json = { | 587 | const json: User = { |
560 | id: this.id, | 588 | id: this.id, |
561 | username: this.username, | 589 | username: this.username, |
562 | email: this.email, | 590 | email: this.email, |
591 | theme: getThemeOrDefault(this.theme, DEFAULT_USER_THEME_NAME), | ||
592 | |||
563 | pendingEmail: this.pendingEmail, | 593 | pendingEmail: this.pendingEmail, |
564 | emailVerified: this.emailVerified, | 594 | emailVerified: this.emailVerified, |
595 | |||
565 | nsfwPolicy: this.nsfwPolicy, | 596 | nsfwPolicy: this.nsfwPolicy, |
566 | webTorrentEnabled: this.webTorrentEnabled, | 597 | webTorrentEnabled: this.webTorrentEnabled, |
567 | videosHistoryEnabled: this.videosHistoryEnabled, | 598 | videosHistoryEnabled: this.videosHistoryEnabled, |
568 | autoPlayVideo: this.autoPlayVideo, | 599 | autoPlayVideo: this.autoPlayVideo, |
569 | videoLanguages: this.videoLanguages, | 600 | videoLanguages: this.videoLanguages, |
601 | |||
570 | role: this.role, | 602 | role: this.role, |
571 | theme: getThemeOrDefault(this.theme, DEFAULT_USER_THEME_NAME), | ||
572 | roleLabel: USER_ROLE_LABELS[ this.role ], | 603 | roleLabel: USER_ROLE_LABELS[ this.role ], |
604 | |||
573 | videoQuota: this.videoQuota, | 605 | videoQuota: this.videoQuota, |
574 | videoQuotaDaily: this.videoQuotaDaily, | 606 | videoQuotaDaily: this.videoQuotaDaily, |
575 | createdAt: this.createdAt, | 607 | videoQuotaUsed: videoQuotaUsed !== undefined |
608 | ? parseInt(videoQuotaUsed + '', 10) | ||
609 | : undefined, | ||
610 | videoQuotaUsedDaily: videoQuotaUsedDaily !== undefined | ||
611 | ? parseInt(videoQuotaUsedDaily + '', 10) | ||
612 | : undefined, | ||
613 | |||
614 | noInstanceConfigWarningModal: this.noInstanceConfigWarningModal, | ||
615 | noWelcomeModal: this.noWelcomeModal, | ||
616 | |||
576 | blocked: this.blocked, | 617 | blocked: this.blocked, |
577 | blockedReason: this.blockedReason, | 618 | blockedReason: this.blockedReason, |
619 | |||
578 | account: this.Account.toFormattedJSON(), | 620 | account: this.Account.toFormattedJSON(), |
579 | notificationSettings: this.NotificationSetting ? this.NotificationSetting.toFormattedJSON() : undefined, | 621 | |
622 | notificationSettings: this.NotificationSetting | ||
623 | ? this.NotificationSetting.toFormattedJSON() | ||
624 | : undefined, | ||
625 | |||
580 | videoChannels: [], | 626 | videoChannels: [], |
581 | videoQuotaUsed: videoQuotaUsed !== undefined | 627 | |
582 | ? parseInt(videoQuotaUsed + '', 10) | 628 | createdAt: this.createdAt |
583 | : undefined, | ||
584 | videoQuotaUsedDaily: videoQuotaUsedDaily !== undefined | ||
585 | ? parseInt(videoQuotaUsedDaily + '', 10) | ||
586 | : undefined | ||
587 | } | 629 | } |
588 | 630 | ||
589 | if (parameters.withAdminFlags) { | 631 | if (parameters.withAdminFlags) { |
diff --git a/server/models/activitypub/actor-follow.ts b/server/models/activitypub/actor-follow.ts index 51b09e09b..8498692f0 100644 --- a/server/models/activitypub/actor-follow.ts +++ b/server/models/activitypub/actor-follow.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import * as Bluebird from 'bluebird' | 1 | import * as Bluebird from 'bluebird' |
2 | import { values } from 'lodash' | 2 | import { values, difference } from 'lodash' |
3 | import { | 3 | import { |
4 | AfterCreate, | 4 | AfterCreate, |
5 | AfterDestroy, | 5 | AfterDestroy, |
@@ -21,13 +21,20 @@ import { FollowState } from '../../../shared/models/actors' | |||
21 | import { ActorFollow } from '../../../shared/models/actors/follow.model' | 21 | import { ActorFollow } from '../../../shared/models/actors/follow.model' |
22 | import { logger } from '../../helpers/logger' | 22 | import { logger } from '../../helpers/logger' |
23 | import { getServerActor } from '../../helpers/utils' | 23 | import { getServerActor } from '../../helpers/utils' |
24 | import { ACTOR_FOLLOW_SCORE, FOLLOW_STATES } from '../../initializers/constants' | 24 | import { ACTOR_FOLLOW_SCORE, FOLLOW_STATES, SERVER_ACTOR_NAME } from '../../initializers/constants' |
25 | import { ServerModel } from '../server/server' | 25 | import { ServerModel } from '../server/server' |
26 | import { createSafeIn, getSort } from '../utils' | 26 | import { createSafeIn, getSort } from '../utils' |
27 | import { ActorModel, unusedActorAttributesForAPI } from './actor' | 27 | import { ActorModel, unusedActorAttributesForAPI } from './actor' |
28 | import { VideoChannelModel } from '../video/video-channel' | 28 | import { VideoChannelModel } from '../video/video-channel' |
29 | import { AccountModel } from '../account/account' | 29 | import { AccountModel } from '../account/account' |
30 | import { IncludeOptions, Op, Transaction, QueryTypes } from 'sequelize' | 30 | import { IncludeOptions, Op, QueryTypes, Transaction } from 'sequelize' |
31 | import { | ||
32 | MActorFollowActorsDefault, | ||
33 | MActorFollowActorsDefaultSubscription, | ||
34 | MActorFollowFollowingHost, | ||
35 | MActorFollowFormattable, | ||
36 | MActorFollowSubscriptions | ||
37 | } from '@server/typings/models' | ||
31 | 38 | ||
32 | @Table({ | 39 | @Table({ |
33 | tableName: 'actorFollow', | 40 | tableName: 'actorFollow', |
@@ -143,7 +150,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> { | |||
143 | if (numberOfActorFollowsRemoved) logger.info('Removed bad %d actor follows.', numberOfActorFollowsRemoved) | 150 | if (numberOfActorFollowsRemoved) logger.info('Removed bad %d actor follows.', numberOfActorFollowsRemoved) |
144 | } | 151 | } |
145 | 152 | ||
146 | static loadByActorAndTarget (actorId: number, targetActorId: number, t?: Transaction) { | 153 | static loadByActorAndTarget (actorId: number, targetActorId: number, t?: Transaction): Bluebird<MActorFollowActorsDefault> { |
147 | const query = { | 154 | const query = { |
148 | where: { | 155 | where: { |
149 | actorId, | 156 | actorId, |
@@ -167,7 +174,12 @@ export class ActorFollowModel extends Model<ActorFollowModel> { | |||
167 | return ActorFollowModel.findOne(query) | 174 | return ActorFollowModel.findOne(query) |
168 | } | 175 | } |
169 | 176 | ||
170 | static loadByActorAndTargetNameAndHostForAPI (actorId: number, targetName: string, targetHost: string, t?: Transaction) { | 177 | static loadByActorAndTargetNameAndHostForAPI ( |
178 | actorId: number, | ||
179 | targetName: string, | ||
180 | targetHost: string, | ||
181 | t?: Transaction | ||
182 | ): Bluebird<MActorFollowActorsDefaultSubscription> { | ||
171 | const actorFollowingPartInclude: IncludeOptions = { | 183 | const actorFollowingPartInclude: IncludeOptions = { |
172 | model: ActorModel, | 184 | model: ActorModel, |
173 | required: true, | 185 | required: true, |
@@ -220,7 +232,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> { | |||
220 | }) | 232 | }) |
221 | } | 233 | } |
222 | 234 | ||
223 | static listSubscribedIn (actorId: number, targets: { name: string, host?: string }[]) { | 235 | static listSubscribedIn (actorId: number, targets: { name: string, host?: string }[]): Bluebird<MActorFollowFollowingHost[]> { |
224 | const whereTab = targets | 236 | const whereTab = targets |
225 | .map(t => { | 237 | .map(t => { |
226 | if (t.host) { | 238 | if (t.host) { |
@@ -314,7 +326,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> { | |||
314 | ] | 326 | ] |
315 | } | 327 | } |
316 | 328 | ||
317 | return ActorFollowModel.findAndCountAll(query) | 329 | return ActorFollowModel.findAndCountAll<MActorFollowActorsDefault>(query) |
318 | .then(({ rows, count }) => { | 330 | .then(({ rows, count }) => { |
319 | return { | 331 | return { |
320 | data: rows, | 332 | data: rows, |
@@ -357,7 +369,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> { | |||
357 | ] | 369 | ] |
358 | } | 370 | } |
359 | 371 | ||
360 | return ActorFollowModel.findAndCountAll(query) | 372 | return ActorFollowModel.findAndCountAll<MActorFollowActorsDefault>(query) |
361 | .then(({ rows, count }) => { | 373 | .then(({ rows, count }) => { |
362 | return { | 374 | return { |
363 | data: rows, | 375 | data: rows, |
@@ -414,7 +426,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> { | |||
414 | ] | 426 | ] |
415 | } | 427 | } |
416 | 428 | ||
417 | return ActorFollowModel.findAndCountAll(query) | 429 | return ActorFollowModel.findAndCountAll<MActorFollowSubscriptions>(query) |
418 | .then(({ rows, count }) => { | 430 | .then(({ rows, count }) => { |
419 | return { | 431 | return { |
420 | data: rows.map(r => r.ActorFollowing.VideoChannel), | 432 | data: rows.map(r => r.ActorFollowing.VideoChannel), |
@@ -423,6 +435,45 @@ export class ActorFollowModel extends Model<ActorFollowModel> { | |||
423 | }) | 435 | }) |
424 | } | 436 | } |
425 | 437 | ||
438 | static async keepUnfollowedInstance (hosts: string[]) { | ||
439 | const followerId = (await getServerActor()).id | ||
440 | |||
441 | const query = { | ||
442 | attributes: [ 'id' ], | ||
443 | where: { | ||
444 | actorId: followerId | ||
445 | }, | ||
446 | include: [ | ||
447 | { | ||
448 | attributes: [ 'id' ], | ||
449 | model: ActorModel.unscoped(), | ||
450 | required: true, | ||
451 | as: 'ActorFollowing', | ||
452 | where: { | ||
453 | preferredUsername: SERVER_ACTOR_NAME | ||
454 | }, | ||
455 | include: [ | ||
456 | { | ||
457 | attributes: [ 'host' ], | ||
458 | model: ServerModel.unscoped(), | ||
459 | required: true, | ||
460 | where: { | ||
461 | host: { | ||
462 | [Op.in]: hosts | ||
463 | } | ||
464 | } | ||
465 | } | ||
466 | ] | ||
467 | } | ||
468 | ] | ||
469 | } | ||
470 | |||
471 | const res = await ActorFollowModel.findAll(query) | ||
472 | const followedHosts = res.map(row => row.ActorFollowing.Server.host) | ||
473 | |||
474 | return difference(hosts, followedHosts) | ||
475 | } | ||
476 | |||
426 | static listAcceptedFollowerUrlsForAP (actorIds: number[], t: Transaction, start?: number, count?: number) { | 477 | static listAcceptedFollowerUrlsForAP (actorIds: number[], t: Transaction, start?: number, count?: number) { |
427 | return ActorFollowModel.createListAcceptedFollowForApiQuery('followers', actorIds, t, start, count) | 478 | return ActorFollowModel.createListAcceptedFollowForApiQuery('followers', actorIds, t, start, count) |
428 | } | 479 | } |
@@ -569,7 +620,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> { | |||
569 | return ActorFollowModel.findAll(query) | 620 | return ActorFollowModel.findAll(query) |
570 | } | 621 | } |
571 | 622 | ||
572 | toFormattedJSON (): ActorFollow { | 623 | toFormattedJSON (this: MActorFollowFormattable): ActorFollow { |
573 | const follower = this.ActorFollower.toFormattedJSON() | 624 | const follower = this.ActorFollower.toFormattedJSON() |
574 | const following = this.ActorFollowing.toFormattedJSON() | 625 | const following = this.ActorFollowing.toFormattedJSON() |
575 | 626 | ||
diff --git a/server/models/activitypub/actor.ts b/server/models/activitypub/actor.ts index 9cc53f78a..05de1905d 100644 --- a/server/models/activitypub/actor.ts +++ b/server/models/activitypub/actor.ts | |||
@@ -36,6 +36,17 @@ import { isOutdated, throwIfNotValid } from '../utils' | |||
36 | import { VideoChannelModel } from '../video/video-channel' | 36 | import { VideoChannelModel } from '../video/video-channel' |
37 | import { ActorFollowModel } from './actor-follow' | 37 | import { ActorFollowModel } from './actor-follow' |
38 | import { VideoModel } from '../video/video' | 38 | import { VideoModel } from '../video/video' |
39 | import { | ||
40 | MActor, | ||
41 | MActorAccountChannelId, | ||
42 | MActorAP, | ||
43 | MActorFormattable, | ||
44 | MActorFull, | ||
45 | MActorHost, | ||
46 | MActorServer, | ||
47 | MActorSummaryFormattable | ||
48 | } from '../../typings/models' | ||
49 | import * as Bluebird from 'bluebird' | ||
39 | 50 | ||
40 | enum ScopeNames { | 51 | enum ScopeNames { |
41 | FULL = 'FULL' | 52 | FULL = 'FULL' |
@@ -163,8 +174,8 @@ export class ActorModel extends Model<ActorModel> { | |||
163 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) | 174 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) |
164 | inboxUrl: string | 175 | inboxUrl: string |
165 | 176 | ||
166 | @AllowNull(false) | 177 | @AllowNull(true) |
167 | @Is('ActorOutboxUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'outbox url')) | 178 | @Is('ActorOutboxUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'outbox url', true)) |
168 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) | 179 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) |
169 | outboxUrl: string | 180 | outboxUrl: string |
170 | 181 | ||
@@ -173,13 +184,13 @@ export class ActorModel extends Model<ActorModel> { | |||
173 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) | 184 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) |
174 | sharedInboxUrl: string | 185 | sharedInboxUrl: string |
175 | 186 | ||
176 | @AllowNull(false) | 187 | @AllowNull(true) |
177 | @Is('ActorFollowersUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'followers url')) | 188 | @Is('ActorFollowersUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'followers url', true)) |
178 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) | 189 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) |
179 | followersUrl: string | 190 | followersUrl: string |
180 | 191 | ||
181 | @AllowNull(false) | 192 | @AllowNull(true) |
182 | @Is('ActorFollowingUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'following url')) | 193 | @Is('ActorFollowingUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'following url', true)) |
183 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) | 194 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) |
184 | followingUrl: string | 195 | followingUrl: string |
185 | 196 | ||
@@ -252,11 +263,15 @@ export class ActorModel extends Model<ActorModel> { | |||
252 | }) | 263 | }) |
253 | VideoChannel: VideoChannelModel | 264 | VideoChannel: VideoChannelModel |
254 | 265 | ||
255 | static load (id: number) { | 266 | static load (id: number): Bluebird<MActor> { |
256 | return ActorModel.unscoped().findByPk(id) | 267 | return ActorModel.unscoped().findByPk(id) |
257 | } | 268 | } |
258 | 269 | ||
259 | static loadAccountActorByVideoId (videoId: number, transaction: Sequelize.Transaction) { | 270 | static loadFull (id: number): Bluebird<MActorFull> { |
271 | return ActorModel.scope(ScopeNames.FULL).findByPk(id) | ||
272 | } | ||
273 | |||
274 | static loadFromAccountByVideoId (videoId: number, transaction: Sequelize.Transaction): Bluebird<MActor> { | ||
260 | const query = { | 275 | const query = { |
261 | include: [ | 276 | include: [ |
262 | { | 277 | { |
@@ -300,7 +315,7 @@ export class ActorModel extends Model<ActorModel> { | |||
300 | .then(a => !!a) | 315 | .then(a => !!a) |
301 | } | 316 | } |
302 | 317 | ||
303 | static listByFollowersUrls (followersUrls: string[], transaction?: Sequelize.Transaction) { | 318 | static listByFollowersUrls (followersUrls: string[], transaction?: Sequelize.Transaction): Bluebird<MActorFull[]> { |
304 | const query = { | 319 | const query = { |
305 | where: { | 320 | where: { |
306 | followersUrl: { | 321 | followersUrl: { |
@@ -313,7 +328,7 @@ export class ActorModel extends Model<ActorModel> { | |||
313 | return ActorModel.scope(ScopeNames.FULL).findAll(query) | 328 | return ActorModel.scope(ScopeNames.FULL).findAll(query) |
314 | } | 329 | } |
315 | 330 | ||
316 | static loadLocalByName (preferredUsername: string, transaction?: Sequelize.Transaction) { | 331 | static loadLocalByName (preferredUsername: string, transaction?: Sequelize.Transaction): Bluebird<MActorFull> { |
317 | const query = { | 332 | const query = { |
318 | where: { | 333 | where: { |
319 | preferredUsername, | 334 | preferredUsername, |
@@ -325,7 +340,7 @@ export class ActorModel extends Model<ActorModel> { | |||
325 | return ActorModel.scope(ScopeNames.FULL).findOne(query) | 340 | return ActorModel.scope(ScopeNames.FULL).findOne(query) |
326 | } | 341 | } |
327 | 342 | ||
328 | static loadByNameAndHost (preferredUsername: string, host: string) { | 343 | static loadByNameAndHost (preferredUsername: string, host: string): Bluebird<MActorFull> { |
329 | const query = { | 344 | const query = { |
330 | where: { | 345 | where: { |
331 | preferredUsername | 346 | preferredUsername |
@@ -344,7 +359,7 @@ export class ActorModel extends Model<ActorModel> { | |||
344 | return ActorModel.scope(ScopeNames.FULL).findOne(query) | 359 | return ActorModel.scope(ScopeNames.FULL).findOne(query) |
345 | } | 360 | } |
346 | 361 | ||
347 | static loadByUrl (url: string, transaction?: Sequelize.Transaction) { | 362 | static loadByUrl (url: string, transaction?: Sequelize.Transaction): Bluebird<MActorAccountChannelId> { |
348 | const query = { | 363 | const query = { |
349 | where: { | 364 | where: { |
350 | url | 365 | url |
@@ -367,7 +382,7 @@ export class ActorModel extends Model<ActorModel> { | |||
367 | return ActorModel.unscoped().findOne(query) | 382 | return ActorModel.unscoped().findOne(query) |
368 | } | 383 | } |
369 | 384 | ||
370 | static loadByUrlAndPopulateAccountAndChannel (url: string, transaction?: Sequelize.Transaction) { | 385 | static loadByUrlAndPopulateAccountAndChannel (url: string, transaction?: Sequelize.Transaction): Bluebird<MActorFull> { |
371 | const query = { | 386 | const query = { |
372 | where: { | 387 | where: { |
373 | url | 388 | url |
@@ -387,35 +402,35 @@ export class ActorModel extends Model<ActorModel> { | |||
387 | }) | 402 | }) |
388 | } | 403 | } |
389 | 404 | ||
390 | toFormattedJSON () { | 405 | toFormattedSummaryJSON (this: MActorSummaryFormattable) { |
391 | let avatar: Avatar = null | 406 | let avatar: Avatar = null |
392 | if (this.Avatar) { | 407 | if (this.Avatar) { |
393 | avatar = this.Avatar.toFormattedJSON() | 408 | avatar = this.Avatar.toFormattedJSON() |
394 | } | 409 | } |
395 | 410 | ||
396 | return { | 411 | return { |
397 | id: this.id, | ||
398 | url: this.url, | 412 | url: this.url, |
399 | name: this.preferredUsername, | 413 | name: this.preferredUsername, |
400 | host: this.getHost(), | 414 | host: this.getHost(), |
415 | avatar | ||
416 | } | ||
417 | } | ||
418 | |||
419 | toFormattedJSON (this: MActorFormattable) { | ||
420 | const base = this.toFormattedSummaryJSON() | ||
421 | |||
422 | return Object.assign(base, { | ||
423 | id: this.id, | ||
401 | hostRedundancyAllowed: this.getRedundancyAllowed(), | 424 | hostRedundancyAllowed: this.getRedundancyAllowed(), |
402 | followingCount: this.followingCount, | 425 | followingCount: this.followingCount, |
403 | followersCount: this.followersCount, | 426 | followersCount: this.followersCount, |
404 | avatar, | ||
405 | createdAt: this.createdAt, | 427 | createdAt: this.createdAt, |
406 | updatedAt: this.updatedAt | 428 | updatedAt: this.updatedAt |
407 | } | 429 | }) |
408 | } | 430 | } |
409 | 431 | ||
410 | toActivityPubObject (name: string, type: 'Account' | 'Application' | 'VideoChannel') { | 432 | toActivityPubObject (this: MActorAP, name: string) { |
411 | let activityPubType | 433 | let activityPubType |
412 | if (type === 'Account') { | ||
413 | activityPubType = 'Person' as 'Person' | ||
414 | } else if (type === 'Application') { | ||
415 | activityPubType = 'Application' as 'Application' | ||
416 | } else { // VideoChannel | ||
417 | activityPubType = 'Group' as 'Group' | ||
418 | } | ||
419 | 434 | ||
420 | let icon = undefined | 435 | let icon = undefined |
421 | if (this.avatarId) { | 436 | if (this.avatarId) { |
@@ -428,7 +443,7 @@ export class ActorModel extends Model<ActorModel> { | |||
428 | } | 443 | } |
429 | 444 | ||
430 | const json = { | 445 | const json = { |
431 | type: activityPubType, | 446 | type: this.type, |
432 | id: this.url, | 447 | id: this.url, |
433 | following: this.getFollowingUrl(), | 448 | following: this.getFollowingUrl(), |
434 | followers: this.getFollowersUrl(), | 449 | followers: this.getFollowersUrl(), |
@@ -494,7 +509,7 @@ export class ActorModel extends Model<ActorModel> { | |||
494 | return this.serverId === null | 509 | return this.serverId === null |
495 | } | 510 | } |
496 | 511 | ||
497 | getWebfingerUrl () { | 512 | getWebfingerUrl (this: MActorServer) { |
498 | return 'acct:' + this.preferredUsername + '@' + this.getHost() | 513 | return 'acct:' + this.preferredUsername + '@' + this.getHost() |
499 | } | 514 | } |
500 | 515 | ||
@@ -502,7 +517,7 @@ export class ActorModel extends Model<ActorModel> { | |||
502 | return this.Server ? `${this.preferredUsername}@${this.Server.host}` : this.preferredUsername | 517 | return this.Server ? `${this.preferredUsername}@${this.Server.host}` : this.preferredUsername |
503 | } | 518 | } |
504 | 519 | ||
505 | getHost () { | 520 | getHost (this: MActorHost) { |
506 | return this.Server ? this.Server.host : WEBSERVER.HOST | 521 | return this.Server ? this.Server.host : WEBSERVER.HOST |
507 | } | 522 | } |
508 | 523 | ||
diff --git a/server/models/avatar/avatar.ts b/server/models/avatar/avatar.ts index b40144592..950e4b181 100644 --- a/server/models/avatar/avatar.ts +++ b/server/models/avatar/avatar.ts | |||
@@ -7,6 +7,7 @@ import { remove } from 'fs-extra' | |||
7 | import { CONFIG } from '../../initializers/config' | 7 | import { CONFIG } from '../../initializers/config' |
8 | import { throwIfNotValid } from '../utils' | 8 | import { throwIfNotValid } from '../utils' |
9 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' | 9 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' |
10 | import { MAvatarFormattable } from '@server/typings/models' | ||
10 | 11 | ||
11 | @Table({ | 12 | @Table({ |
12 | tableName: 'avatar', | 13 | tableName: 'avatar', |
@@ -57,7 +58,7 @@ export class AvatarModel extends Model<AvatarModel> { | |||
57 | return AvatarModel.findOne(query) | 58 | return AvatarModel.findOne(query) |
58 | } | 59 | } |
59 | 60 | ||
60 | toFormattedJSON (): Avatar { | 61 | toFormattedJSON (this: MAvatarFormattable): Avatar { |
61 | return { | 62 | return { |
62 | path: this.getStaticPath(), | 63 | path: this.getStaticPath(), |
63 | createdAt: this.createdAt, | 64 | createdAt: this.createdAt, |
diff --git a/server/models/oauth/oauth-token.ts b/server/models/oauth/oauth-token.ts index 903d551df..b680be237 100644 --- a/server/models/oauth/oauth-token.ts +++ b/server/models/oauth/oauth-token.ts | |||
@@ -18,6 +18,8 @@ import { Transaction } from 'sequelize' | |||
18 | import { AccountModel } from '../account/account' | 18 | import { AccountModel } from '../account/account' |
19 | import { ActorModel } from '../activitypub/actor' | 19 | import { ActorModel } from '../activitypub/actor' |
20 | import { clearCacheByToken } from '../../lib/oauth-model' | 20 | import { clearCacheByToken } from '../../lib/oauth-model' |
21 | import * as Bluebird from 'bluebird' | ||
22 | import { MOAuthTokenUser } from '@server/typings/models/oauth/oauth-token' | ||
21 | 23 | ||
22 | export type OAuthTokenInfo = { | 24 | export type OAuthTokenInfo = { |
23 | refreshToken: string | 25 | refreshToken: string |
@@ -160,7 +162,7 @@ export class OAuthTokenModel extends Model<OAuthTokenModel> { | |||
160 | }) | 162 | }) |
161 | } | 163 | } |
162 | 164 | ||
163 | static getByTokenAndPopulateUser (bearerToken: string) { | 165 | static getByTokenAndPopulateUser (bearerToken: string): Bluebird<MOAuthTokenUser> { |
164 | const query = { | 166 | const query = { |
165 | where: { | 167 | where: { |
166 | accessToken: bearerToken | 168 | accessToken: bearerToken |
@@ -170,13 +172,13 @@ export class OAuthTokenModel extends Model<OAuthTokenModel> { | |||
170 | return OAuthTokenModel.scope(ScopeNames.WITH_USER) | 172 | return OAuthTokenModel.scope(ScopeNames.WITH_USER) |
171 | .findOne(query) | 173 | .findOne(query) |
172 | .then(token => { | 174 | .then(token => { |
173 | if (token) token[ 'user' ] = token.User | 175 | if (!token) return null |
174 | 176 | ||
175 | return token | 177 | return Object.assign(token, { user: token.User }) |
176 | }) | 178 | }) |
177 | } | 179 | } |
178 | 180 | ||
179 | static getByRefreshTokenAndPopulateUser (refreshToken: string) { | 181 | static getByRefreshTokenAndPopulateUser (refreshToken: string): Bluebird<MOAuthTokenUser> { |
180 | const query = { | 182 | const query = { |
181 | where: { | 183 | where: { |
182 | refreshToken: refreshToken | 184 | refreshToken: refreshToken |
@@ -186,12 +188,9 @@ export class OAuthTokenModel extends Model<OAuthTokenModel> { | |||
186 | return OAuthTokenModel.scope(ScopeNames.WITH_USER) | 188 | return OAuthTokenModel.scope(ScopeNames.WITH_USER) |
187 | .findOne(query) | 189 | .findOne(query) |
188 | .then(token => { | 190 | .then(token => { |
189 | if (token) { | 191 | if (!token) return new OAuthTokenModel() |
190 | token['user'] = token.User | 192 | |
191 | return token | 193 | return Object.assign(token, { user: token.User }) |
192 | } else { | ||
193 | return new OAuthTokenModel() | ||
194 | } | ||
195 | }) | 194 | }) |
196 | } | 195 | } |
197 | 196 | ||
diff --git a/server/models/redundancy/video-redundancy.ts b/server/models/redundancy/video-redundancy.ts index 3df1c4f9c..61d9a5612 100644 --- a/server/models/redundancy/video-redundancy.ts +++ b/server/models/redundancy/video-redundancy.ts | |||
@@ -30,6 +30,7 @@ import * as Bluebird from 'bluebird' | |||
30 | import { col, FindOptions, fn, literal, Op, Transaction } from 'sequelize' | 30 | import { col, FindOptions, fn, literal, Op, Transaction } from 'sequelize' |
31 | import { VideoStreamingPlaylistModel } from '../video/video-streaming-playlist' | 31 | import { VideoStreamingPlaylistModel } from '../video/video-streaming-playlist' |
32 | import { CONFIG } from '../../initializers/config' | 32 | import { CONFIG } from '../../initializers/config' |
33 | import { MVideoRedundancy, MVideoRedundancyAP, MVideoRedundancyVideo } from '@server/typings/models' | ||
33 | 34 | ||
34 | export enum ScopeNames { | 35 | export enum ScopeNames { |
35 | WITH_VIDEO = 'WITH_VIDEO' | 36 | WITH_VIDEO = 'WITH_VIDEO' |
@@ -166,7 +167,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
166 | return undefined | 167 | return undefined |
167 | } | 168 | } |
168 | 169 | ||
169 | static async loadLocalByFileId (videoFileId: number) { | 170 | static async loadLocalByFileId (videoFileId: number): Promise<MVideoRedundancyVideo> { |
170 | const actor = await getServerActor() | 171 | const actor = await getServerActor() |
171 | 172 | ||
172 | const query = { | 173 | const query = { |
@@ -179,7 +180,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
179 | return VideoRedundancyModel.scope(ScopeNames.WITH_VIDEO).findOne(query) | 180 | return VideoRedundancyModel.scope(ScopeNames.WITH_VIDEO).findOne(query) |
180 | } | 181 | } |
181 | 182 | ||
182 | static async loadLocalByStreamingPlaylistId (videoStreamingPlaylistId: number) { | 183 | static async loadLocalByStreamingPlaylistId (videoStreamingPlaylistId: number): Promise<MVideoRedundancyVideo> { |
183 | const actor = await getServerActor() | 184 | const actor = await getServerActor() |
184 | 185 | ||
185 | const query = { | 186 | const query = { |
@@ -192,7 +193,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
192 | return VideoRedundancyModel.scope(ScopeNames.WITH_VIDEO).findOne(query) | 193 | return VideoRedundancyModel.scope(ScopeNames.WITH_VIDEO).findOne(query) |
193 | } | 194 | } |
194 | 195 | ||
195 | static loadByUrl (url: string, transaction?: Transaction) { | 196 | static loadByUrl (url: string, transaction?: Transaction): Bluebird<MVideoRedundancy> { |
196 | const query = { | 197 | const query = { |
197 | where: { | 198 | where: { |
198 | url | 199 | url |
@@ -306,7 +307,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
306 | return VideoRedundancyModel.getVideoSample(VideoModel.unscoped().findAll(query)) | 307 | return VideoRedundancyModel.getVideoSample(VideoModel.unscoped().findAll(query)) |
307 | } | 308 | } |
308 | 309 | ||
309 | static async loadOldestLocalThatAlreadyExpired (strategy: VideoRedundancyStrategy, expiresAfterMs: number) { | 310 | static async loadOldestLocalExpired (strategy: VideoRedundancyStrategy, expiresAfterMs: number): Promise<MVideoRedundancyVideo> { |
310 | const expiredDate = new Date() | 311 | const expiredDate = new Date() |
311 | expiredDate.setMilliseconds(expiredDate.getMilliseconds() - expiresAfterMs) | 312 | expiredDate.setMilliseconds(expiredDate.getMilliseconds() - expiresAfterMs) |
312 | 313 | ||
@@ -487,7 +488,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
487 | return !!this.strategy | 488 | return !!this.strategy |
488 | } | 489 | } |
489 | 490 | ||
490 | toActivityPubObject (): CacheFileObject { | 491 | toActivityPubObject (this: MVideoRedundancyAP): CacheFileObject { |
491 | if (this.VideoStreamingPlaylist) { | 492 | if (this.VideoStreamingPlaylist) { |
492 | return { | 493 | return { |
493 | id: this.url, | 494 | id: this.url, |
diff --git a/server/models/server/plugin.ts b/server/models/server/plugin.ts index a15f9a7e2..d094da1f5 100644 --- a/server/models/server/plugin.ts +++ b/server/models/server/plugin.ts | |||
@@ -11,6 +11,8 @@ import { PluginType } from '../../../shared/models/plugins/plugin.type' | |||
11 | import { PeerTubePlugin } from '../../../shared/models/plugins/peertube-plugin.model' | 11 | import { PeerTubePlugin } from '../../../shared/models/plugins/peertube-plugin.model' |
12 | import { FindAndCountOptions, json } from 'sequelize' | 12 | import { FindAndCountOptions, json } from 'sequelize' |
13 | import { RegisterServerSettingOptions } from '../../../shared/models/plugins/register-server-setting.model' | 13 | import { RegisterServerSettingOptions } from '../../../shared/models/plugins/register-server-setting.model' |
14 | import * as Bluebird from 'bluebird' | ||
15 | import { MPlugin, MPluginFormattable } from '@server/typings/models' | ||
14 | 16 | ||
15 | @DefaultScope(() => ({ | 17 | @DefaultScope(() => ({ |
16 | attributes: { | 18 | attributes: { |
@@ -85,7 +87,7 @@ export class PluginModel extends Model<PluginModel> { | |||
85 | @UpdatedAt | 87 | @UpdatedAt |
86 | updatedAt: Date | 88 | updatedAt: Date |
87 | 89 | ||
88 | static listEnabledPluginsAndThemes () { | 90 | static listEnabledPluginsAndThemes (): Bluebird<MPlugin[]> { |
89 | const query = { | 91 | const query = { |
90 | where: { | 92 | where: { |
91 | enabled: true, | 93 | enabled: true, |
@@ -96,7 +98,7 @@ export class PluginModel extends Model<PluginModel> { | |||
96 | return PluginModel.findAll(query) | 98 | return PluginModel.findAll(query) |
97 | } | 99 | } |
98 | 100 | ||
99 | static loadByNpmName (npmName: string) { | 101 | static loadByNpmName (npmName: string): Bluebird<MPlugin> { |
100 | const name = this.normalizePluginName(npmName) | 102 | const name = this.normalizePluginName(npmName) |
101 | const type = this.getTypeFromNpmName(npmName) | 103 | const type = this.getTypeFromNpmName(npmName) |
102 | 104 | ||
@@ -206,13 +208,13 @@ export class PluginModel extends Model<PluginModel> { | |||
206 | if (options.pluginType) query.where['type'] = options.pluginType | 208 | if (options.pluginType) query.where['type'] = options.pluginType |
207 | 209 | ||
208 | return PluginModel | 210 | return PluginModel |
209 | .findAndCountAll(query) | 211 | .findAndCountAll<MPlugin>(query) |
210 | .then(({ rows, count }) => { | 212 | .then(({ rows, count }) => { |
211 | return { total: count, data: rows } | 213 | return { total: count, data: rows } |
212 | }) | 214 | }) |
213 | } | 215 | } |
214 | 216 | ||
215 | static listInstalled () { | 217 | static listInstalled (): Bluebird<MPlugin[]> { |
216 | const query = { | 218 | const query = { |
217 | where: { | 219 | where: { |
218 | uninstalled: false | 220 | uninstalled: false |
@@ -251,7 +253,7 @@ export class PluginModel extends Model<PluginModel> { | |||
251 | return result | 253 | return result |
252 | } | 254 | } |
253 | 255 | ||
254 | toFormattedJSON (): PeerTubePlugin { | 256 | toFormattedJSON (this: MPluginFormattable): PeerTubePlugin { |
255 | return { | 257 | return { |
256 | name: this.name, | 258 | name: this.name, |
257 | type: this.type, | 259 | type: this.type, |
diff --git a/server/models/server/server-blocklist.ts b/server/models/server/server-blocklist.ts index 5138b0f76..3e9687191 100644 --- a/server/models/server/server-blocklist.ts +++ b/server/models/server/server-blocklist.ts | |||
@@ -3,6 +3,8 @@ import { AccountModel } from '../account/account' | |||
3 | import { ServerModel } from './server' | 3 | import { ServerModel } from './server' |
4 | import { ServerBlock } from '../../../shared/models/blocklist' | 4 | import { ServerBlock } from '../../../shared/models/blocklist' |
5 | import { getSort } from '../utils' | 5 | import { getSort } from '../utils' |
6 | import * as Bluebird from 'bluebird' | ||
7 | import { MServerBlocklist, MServerBlocklistAccountServer, MServerBlocklistFormattable } from '@server/typings/models' | ||
6 | 8 | ||
7 | enum ScopeNames { | 9 | enum ScopeNames { |
8 | WITH_ACCOUNT = 'WITH_ACCOUNT', | 10 | WITH_ACCOUNT = 'WITH_ACCOUNT', |
@@ -73,7 +75,7 @@ export class ServerBlocklistModel extends Model<ServerBlocklistModel> { | |||
73 | }) | 75 | }) |
74 | BlockedServer: ServerModel | 76 | BlockedServer: ServerModel |
75 | 77 | ||
76 | static loadByAccountAndHost (accountId: number, host: string) { | 78 | static loadByAccountAndHost (accountId: number, host: string): Bluebird<MServerBlocklist> { |
77 | const query = { | 79 | const query = { |
78 | where: { | 80 | where: { |
79 | accountId | 81 | accountId |
@@ -104,13 +106,13 @@ export class ServerBlocklistModel extends Model<ServerBlocklistModel> { | |||
104 | 106 | ||
105 | return ServerBlocklistModel | 107 | return ServerBlocklistModel |
106 | .scope([ ScopeNames.WITH_ACCOUNT, ScopeNames.WITH_SERVER ]) | 108 | .scope([ ScopeNames.WITH_ACCOUNT, ScopeNames.WITH_SERVER ]) |
107 | .findAndCountAll(query) | 109 | .findAndCountAll<MServerBlocklistAccountServer>(query) |
108 | .then(({ rows, count }) => { | 110 | .then(({ rows, count }) => { |
109 | return { total: count, data: rows } | 111 | return { total: count, data: rows } |
110 | }) | 112 | }) |
111 | } | 113 | } |
112 | 114 | ||
113 | toFormattedJSON (): ServerBlock { | 115 | toFormattedJSON (this: MServerBlocklistFormattable): ServerBlock { |
114 | return { | 116 | return { |
115 | byAccount: this.ByAccount.toFormattedJSON(), | 117 | byAccount: this.ByAccount.toFormattedJSON(), |
116 | blockedServer: this.BlockedServer.toFormattedJSON(), | 118 | blockedServer: this.BlockedServer.toFormattedJSON(), |
diff --git a/server/models/server/server.ts b/server/models/server/server.ts index 1d211f1e0..8b07115f1 100644 --- a/server/models/server/server.ts +++ b/server/models/server/server.ts | |||
@@ -2,8 +2,9 @@ import { AllowNull, Column, CreatedAt, Default, HasMany, Is, Model, Table, Updat | |||
2 | import { isHostValid } from '../../helpers/custom-validators/servers' | 2 | import { isHostValid } from '../../helpers/custom-validators/servers' |
3 | import { ActorModel } from '../activitypub/actor' | 3 | import { ActorModel } from '../activitypub/actor' |
4 | import { throwIfNotValid } from '../utils' | 4 | import { throwIfNotValid } from '../utils' |
5 | import { AccountBlocklistModel } from '../account/account-blocklist' | ||
6 | import { ServerBlocklistModel } from './server-blocklist' | 5 | import { ServerBlocklistModel } from './server-blocklist' |
6 | import * as Bluebird from 'bluebird' | ||
7 | import { MServer, MServerFormattable } from '@server/typings/models/server' | ||
7 | 8 | ||
8 | @Table({ | 9 | @Table({ |
9 | tableName: 'server', | 10 | tableName: 'server', |
@@ -50,7 +51,17 @@ export class ServerModel extends Model<ServerModel> { | |||
50 | }) | 51 | }) |
51 | BlockedByAccounts: ServerBlocklistModel[] | 52 | BlockedByAccounts: ServerBlocklistModel[] |
52 | 53 | ||
53 | static loadByHost (host: string) { | 54 | static load (id: number): Bluebird<MServer> { |
55 | const query = { | ||
56 | where: { | ||
57 | id | ||
58 | } | ||
59 | } | ||
60 | |||
61 | return ServerModel.findOne(query) | ||
62 | } | ||
63 | |||
64 | static loadByHost (host: string): Bluebird<MServer> { | ||
54 | const query = { | 65 | const query = { |
55 | where: { | 66 | where: { |
56 | host | 67 | host |
@@ -64,7 +75,7 @@ export class ServerModel extends Model<ServerModel> { | |||
64 | return this.BlockedByAccounts && this.BlockedByAccounts.length !== 0 | 75 | return this.BlockedByAccounts && this.BlockedByAccounts.length !== 0 |
65 | } | 76 | } |
66 | 77 | ||
67 | toFormattedJSON () { | 78 | toFormattedJSON (this: MServerFormattable) { |
68 | return { | 79 | return { |
69 | host: this.host | 80 | host: this.host |
70 | } | 81 | } |
diff --git a/server/models/video/schedule-video-update.ts b/server/models/video/schedule-video-update.ts index 603d55692..fc2a424aa 100644 --- a/server/models/video/schedule-video-update.ts +++ b/server/models/video/schedule-video-update.ts | |||
@@ -2,6 +2,7 @@ import { AllowNull, BelongsTo, Column, CreatedAt, Default, ForeignKey, Model, Ta | |||
2 | import { ScopeNames as VideoScopeNames, VideoModel } from './video' | 2 | import { ScopeNames as VideoScopeNames, VideoModel } from './video' |
3 | import { VideoPrivacy } from '../../../shared/models/videos' | 3 | import { VideoPrivacy } from '../../../shared/models/videos' |
4 | import { Op, Transaction } from 'sequelize' | 4 | import { Op, Transaction } from 'sequelize' |
5 | import { MScheduleVideoUpdateFormattable } from '@server/typings/models' | ||
5 | 6 | ||
6 | @Table({ | 7 | @Table({ |
7 | tableName: 'scheduleVideoUpdate', | 8 | tableName: 'scheduleVideoUpdate', |
@@ -96,7 +97,7 @@ export class ScheduleVideoUpdateModel extends Model<ScheduleVideoUpdateModel> { | |||
96 | return ScheduleVideoUpdateModel.destroy(query) | 97 | return ScheduleVideoUpdateModel.destroy(query) |
97 | } | 98 | } |
98 | 99 | ||
99 | toFormattedJSON () { | 100 | toFormattedJSON (this: MScheduleVideoUpdateFormattable) { |
100 | return { | 101 | return { |
101 | updateAt: this.updateAt, | 102 | updateAt: this.updateAt, |
102 | privacy: this.privacy || undefined | 103 | privacy: this.privacy || undefined |
diff --git a/server/models/video/tag.ts b/server/models/video/tag.ts index 0fc3cfd4c..ed8df8b48 100644 --- a/server/models/video/tag.ts +++ b/server/models/video/tag.ts | |||
@@ -1,11 +1,12 @@ | |||
1 | import * as Bluebird from 'bluebird' | 1 | import * as Bluebird from 'bluebird' |
2 | import { QueryTypes, Transaction } from 'sequelize' | 2 | import { fn, QueryTypes, Transaction, col } from 'sequelize' |
3 | import { AllowNull, BelongsToMany, Column, CreatedAt, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' | 3 | import { AllowNull, BelongsToMany, Column, CreatedAt, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' |
4 | import { isVideoTagValid } from '../../helpers/custom-validators/videos' | 4 | import { isVideoTagValid } from '../../helpers/custom-validators/videos' |
5 | import { throwIfNotValid } from '../utils' | 5 | import { throwIfNotValid } from '../utils' |
6 | import { VideoModel } from './video' | 6 | import { VideoModel } from './video' |
7 | import { VideoTagModel } from './video-tag' | 7 | import { VideoTagModel } from './video-tag' |
8 | import { VideoPrivacy, VideoState } from '../../../shared/models/videos' | 8 | import { VideoPrivacy, VideoState } from '../../../shared/models/videos' |
9 | import { MTag } from '@server/typings/models' | ||
9 | 10 | ||
10 | @Table({ | 11 | @Table({ |
11 | tableName: 'tag', | 12 | tableName: 'tag', |
@@ -14,6 +15,10 @@ import { VideoPrivacy, VideoState } from '../../../shared/models/videos' | |||
14 | { | 15 | { |
15 | fields: [ 'name' ], | 16 | fields: [ 'name' ], |
16 | unique: true | 17 | unique: true |
18 | }, | ||
19 | { | ||
20 | name: 'tag_lower_name', | ||
21 | fields: [ fn('lower', col('name')) ] as any // FIXME: typings | ||
17 | } | 22 | } |
18 | ] | 23 | ] |
19 | }) | 24 | }) |
@@ -37,10 +42,10 @@ export class TagModel extends Model<TagModel> { | |||
37 | }) | 42 | }) |
38 | Videos: VideoModel[] | 43 | Videos: VideoModel[] |
39 | 44 | ||
40 | static findOrCreateTags (tags: string[], transaction: Transaction) { | 45 | static findOrCreateTags (tags: string[], transaction: Transaction): Promise<MTag[]> { |
41 | if (tags === null) return [] | 46 | if (tags === null) return Promise.resolve([]) |
42 | 47 | ||
43 | const tasks: Bluebird<TagModel>[] = [] | 48 | const tasks: Bluebird<MTag>[] = [] |
44 | tags.forEach(tag => { | 49 | tags.forEach(tag => { |
45 | const query = { | 50 | const query = { |
46 | where: { | 51 | where: { |
@@ -52,7 +57,7 @@ export class TagModel extends Model<TagModel> { | |||
52 | transaction | 57 | transaction |
53 | } | 58 | } |
54 | 59 | ||
55 | const promise = TagModel.findOrCreate(query) | 60 | const promise = TagModel.findOrCreate<MTag>(query) |
56 | .then(([ tagInstance ]) => tagInstance) | 61 | .then(([ tagInstance ]) => tagInstance) |
57 | tasks.push(promise) | 62 | tasks.push(promise) |
58 | }) | 63 | }) |
diff --git a/server/models/video/video-abuse.ts b/server/models/video/video-abuse.ts index 1ac7919b3..3636db18d 100644 --- a/server/models/video/video-abuse.ts +++ b/server/models/video/video-abuse.ts | |||
@@ -7,10 +7,13 @@ import { | |||
7 | isVideoAbuseStateValid | 7 | isVideoAbuseStateValid |
8 | } from '../../helpers/custom-validators/video-abuses' | 8 | } from '../../helpers/custom-validators/video-abuses' |
9 | import { AccountModel } from '../account/account' | 9 | import { AccountModel } from '../account/account' |
10 | import { getSort, throwIfNotValid } from '../utils' | 10 | import { buildBlockedAccountSQL, getSort, throwIfNotValid } from '../utils' |
11 | import { VideoModel } from './video' | 11 | import { VideoModel } from './video' |
12 | import { VideoAbuseState } from '../../../shared' | 12 | import { VideoAbuseState } from '../../../shared' |
13 | import { CONSTRAINTS_FIELDS, VIDEO_ABUSE_STATES } from '../../initializers/constants' | 13 | import { CONSTRAINTS_FIELDS, VIDEO_ABUSE_STATES } from '../../initializers/constants' |
14 | import { MUserAccountId, MVideoAbuse, MVideoAbuseFormattable, MVideoAbuseVideo } from '../../typings/models' | ||
15 | import * as Bluebird from 'bluebird' | ||
16 | import { literal, Op } from 'sequelize' | ||
14 | 17 | ||
15 | @Table({ | 18 | @Table({ |
16 | tableName: 'videoAbuse', | 19 | tableName: 'videoAbuse', |
@@ -73,7 +76,7 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> { | |||
73 | }) | 76 | }) |
74 | Video: VideoModel | 77 | Video: VideoModel |
75 | 78 | ||
76 | static loadByIdAndVideoId (id: number, videoId: number) { | 79 | static loadByIdAndVideoId (id: number, videoId: number): Bluebird<MVideoAbuse> { |
77 | const query = { | 80 | const query = { |
78 | where: { | 81 | where: { |
79 | id, | 82 | id, |
@@ -83,11 +86,25 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> { | |||
83 | return VideoAbuseModel.findOne(query) | 86 | return VideoAbuseModel.findOne(query) |
84 | } | 87 | } |
85 | 88 | ||
86 | static listForApi (start: number, count: number, sort: string) { | 89 | static listForApi (parameters: { |
90 | start: number, | ||
91 | count: number, | ||
92 | sort: string, | ||
93 | serverAccountId: number | ||
94 | user?: MUserAccountId | ||
95 | }) { | ||
96 | const { start, count, sort, user, serverAccountId } = parameters | ||
97 | const userAccountId = user ? user.Account.id : undefined | ||
98 | |||
87 | const query = { | 99 | const query = { |
88 | offset: start, | 100 | offset: start, |
89 | limit: count, | 101 | limit: count, |
90 | order: getSort(sort), | 102 | order: getSort(sort), |
103 | where: { | ||
104 | reporterAccountId: { | ||
105 | [Op.notIn]: literal('(' + buildBlockedAccountSQL(serverAccountId, userAccountId) + ')') | ||
106 | } | ||
107 | }, | ||
91 | include: [ | 108 | include: [ |
92 | { | 109 | { |
93 | model: AccountModel, | 110 | model: AccountModel, |
@@ -106,7 +123,7 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> { | |||
106 | }) | 123 | }) |
107 | } | 124 | } |
108 | 125 | ||
109 | toFormattedJSON (): VideoAbuse { | 126 | toFormattedJSON (this: MVideoAbuseFormattable): VideoAbuse { |
110 | return { | 127 | return { |
111 | id: this.id, | 128 | id: this.id, |
112 | reason: this.reason, | 129 | reason: this.reason, |
@@ -125,7 +142,7 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> { | |||
125 | } | 142 | } |
126 | } | 143 | } |
127 | 144 | ||
128 | toActivityPubObject (): VideoAbuseObject { | 145 | toActivityPubObject (this: MVideoAbuseVideo): VideoAbuseObject { |
129 | return { | 146 | return { |
130 | type: 'Flag' as 'Flag', | 147 | type: 'Flag' as 'Flag', |
131 | content: this.reason, | 148 | content: this.reason, |
diff --git a/server/models/video/video-blacklist.ts b/server/models/video/video-blacklist.ts index cdb725e7a..694983cb3 100644 --- a/server/models/video/video-blacklist.ts +++ b/server/models/video/video-blacklist.ts | |||
@@ -1,12 +1,14 @@ | |||
1 | import { AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' | 1 | import { AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' |
2 | import { getBlacklistSort, getSort, SortType, throwIfNotValid } from '../utils' | 2 | import { getBlacklistSort, SortType, throwIfNotValid } from '../utils' |
3 | import { ScopeNames as VideoModelScopeNames, VideoModel } from './video' | 3 | import { VideoModel } from './video' |
4 | import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from './video-channel' | 4 | import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from './video-channel' |
5 | import { isVideoBlacklistReasonValid, isVideoBlacklistTypeValid } from '../../helpers/custom-validators/video-blacklist' | 5 | import { isVideoBlacklistReasonValid, isVideoBlacklistTypeValid } from '../../helpers/custom-validators/video-blacklist' |
6 | import { VideoBlacklist, VideoBlacklistType } from '../../../shared/models/videos' | 6 | import { VideoBlacklist, VideoBlacklistType } from '../../../shared/models/videos' |
7 | import { CONSTRAINTS_FIELDS } from '../../initializers/constants' | 7 | import { CONSTRAINTS_FIELDS } from '../../initializers/constants' |
8 | import { FindOptions, literal } from 'sequelize' | 8 | import { FindOptions } from 'sequelize' |
9 | import { ThumbnailModel } from './thumbnail' | 9 | import { ThumbnailModel } from './thumbnail' |
10 | import * as Bluebird from 'bluebird' | ||
11 | import { MVideoBlacklist, MVideoBlacklistFormattable } from '@server/typings/models' | ||
10 | 12 | ||
11 | @Table({ | 13 | @Table({ |
12 | tableName: 'videoBlacklist', | 14 | tableName: 'videoBlacklist', |
@@ -98,7 +100,7 @@ export class VideoBlacklistModel extends Model<VideoBlacklistModel> { | |||
98 | }) | 100 | }) |
99 | } | 101 | } |
100 | 102 | ||
101 | static loadByVideoId (id: number) { | 103 | static loadByVideoId (id: number): Bluebird<MVideoBlacklist> { |
102 | const query = { | 104 | const query = { |
103 | where: { | 105 | where: { |
104 | videoId: id | 106 | videoId: id |
@@ -108,7 +110,7 @@ export class VideoBlacklistModel extends Model<VideoBlacklistModel> { | |||
108 | return VideoBlacklistModel.findOne(query) | 110 | return VideoBlacklistModel.findOne(query) |
109 | } | 111 | } |
110 | 112 | ||
111 | toFormattedJSON (): VideoBlacklist { | 113 | toFormattedJSON (this: MVideoBlacklistFormattable): VideoBlacklist { |
112 | return { | 114 | return { |
113 | id: this.id, | 115 | id: this.id, |
114 | createdAt: this.createdAt, | 116 | createdAt: this.createdAt, |
diff --git a/server/models/video/video-caption.ts b/server/models/video/video-caption.ts index a01565851..ad5801768 100644 --- a/server/models/video/video-caption.ts +++ b/server/models/video/video-caption.ts | |||
@@ -21,6 +21,8 @@ import { join } from 'path' | |||
21 | import { logger } from '../../helpers/logger' | 21 | import { logger } from '../../helpers/logger' |
22 | import { remove } from 'fs-extra' | 22 | import { remove } from 'fs-extra' |
23 | import { CONFIG } from '../../initializers/config' | 23 | import { CONFIG } from '../../initializers/config' |
24 | import * as Bluebird from 'bluebird' | ||
25 | import { MVideoCaptionFormattable, MVideoCaptionVideo } from '@server/typings/models' | ||
24 | 26 | ||
25 | export enum ScopeNames { | 27 | export enum ScopeNames { |
26 | WITH_VIDEO_UUID_AND_REMOTE = 'WITH_VIDEO_UUID_AND_REMOTE' | 28 | WITH_VIDEO_UUID_AND_REMOTE = 'WITH_VIDEO_UUID_AND_REMOTE' |
@@ -30,7 +32,7 @@ export enum ScopeNames { | |||
30 | [ScopeNames.WITH_VIDEO_UUID_AND_REMOTE]: { | 32 | [ScopeNames.WITH_VIDEO_UUID_AND_REMOTE]: { |
31 | include: [ | 33 | include: [ |
32 | { | 34 | { |
33 | attributes: [ 'uuid', 'remote' ], | 35 | attributes: [ 'id', 'uuid', 'remote' ], |
34 | model: VideoModel.unscoped(), | 36 | model: VideoModel.unscoped(), |
35 | required: true | 37 | required: true |
36 | } | 38 | } |
@@ -93,7 +95,7 @@ export class VideoCaptionModel extends Model<VideoCaptionModel> { | |||
93 | return undefined | 95 | return undefined |
94 | } | 96 | } |
95 | 97 | ||
96 | static loadByVideoIdAndLanguage (videoId: string | number, language: string) { | 98 | static loadByVideoIdAndLanguage (videoId: string | number, language: string): Bluebird<MVideoCaptionVideo> { |
97 | const videoInclude = { | 99 | const videoInclude = { |
98 | model: VideoModel.unscoped(), | 100 | model: VideoModel.unscoped(), |
99 | attributes: [ 'id', 'remote', 'uuid' ], | 101 | attributes: [ 'id', 'remote', 'uuid' ], |
@@ -122,7 +124,7 @@ export class VideoCaptionModel extends Model<VideoCaptionModel> { | |||
122 | .then(([ caption ]) => caption) | 124 | .then(([ caption ]) => caption) |
123 | } | 125 | } |
124 | 126 | ||
125 | static listVideoCaptions (videoId: number) { | 127 | static listVideoCaptions (videoId: number): Bluebird<MVideoCaptionVideo[]> { |
126 | const query = { | 128 | const query = { |
127 | order: [ [ 'language', 'ASC' ] ] as OrderItem[], | 129 | order: [ [ 'language', 'ASC' ] ] as OrderItem[], |
128 | where: { | 130 | where: { |
@@ -152,7 +154,7 @@ export class VideoCaptionModel extends Model<VideoCaptionModel> { | |||
152 | return this.Video.remote === false | 154 | return this.Video.remote === false |
153 | } | 155 | } |
154 | 156 | ||
155 | toFormattedJSON (): VideoCaption { | 157 | toFormattedJSON (this: MVideoCaptionFormattable): VideoCaption { |
156 | return { | 158 | return { |
157 | language: { | 159 | language: { |
158 | id: this.language, | 160 | id: this.language, |
@@ -162,15 +164,15 @@ export class VideoCaptionModel extends Model<VideoCaptionModel> { | |||
162 | } | 164 | } |
163 | } | 165 | } |
164 | 166 | ||
165 | getCaptionStaticPath () { | 167 | getCaptionStaticPath (this: MVideoCaptionFormattable) { |
166 | return join(LAZY_STATIC_PATHS.VIDEO_CAPTIONS, this.getCaptionName()) | 168 | return join(LAZY_STATIC_PATHS.VIDEO_CAPTIONS, this.getCaptionName()) |
167 | } | 169 | } |
168 | 170 | ||
169 | getCaptionName () { | 171 | getCaptionName (this: MVideoCaptionFormattable) { |
170 | return `${this.Video.uuid}-${this.language}.vtt` | 172 | return `${this.Video.uuid}-${this.language}.vtt` |
171 | } | 173 | } |
172 | 174 | ||
173 | removeCaptionFile () { | 175 | removeCaptionFile (this: MVideoCaptionFormattable) { |
174 | return remove(CONFIG.STORAGE.CAPTIONS_DIR + this.getCaptionName()) | 176 | return remove(CONFIG.STORAGE.CAPTIONS_DIR + this.getCaptionName()) |
175 | } | 177 | } |
176 | } | 178 | } |
diff --git a/server/models/video/video-change-ownership.ts b/server/models/video/video-change-ownership.ts index b545a2f8c..f7a351329 100644 --- a/server/models/video/video-change-ownership.ts +++ b/server/models/video/video-change-ownership.ts | |||
@@ -3,6 +3,8 @@ import { AccountModel } from '../account/account' | |||
3 | import { ScopeNames as VideoScopeNames, VideoModel } from './video' | 3 | import { ScopeNames as VideoScopeNames, VideoModel } from './video' |
4 | import { VideoChangeOwnership, VideoChangeOwnershipStatus } from '../../../shared/models/videos' | 4 | import { VideoChangeOwnership, VideoChangeOwnershipStatus } from '../../../shared/models/videos' |
5 | import { getSort } from '../utils' | 5 | import { getSort } from '../utils' |
6 | import { MVideoChangeOwnershipFormattable, MVideoChangeOwnershipFull } from '@server/typings/models/video/video-change-ownership' | ||
7 | import * as Bluebird from 'bluebird' | ||
6 | 8 | ||
7 | enum ScopeNames { | 9 | enum ScopeNames { |
8 | WITH_ACCOUNTS = 'WITH_ACCOUNTS', | 10 | WITH_ACCOUNTS = 'WITH_ACCOUNTS', |
@@ -108,16 +110,16 @@ export class VideoChangeOwnershipModel extends Model<VideoChangeOwnershipModel> | |||
108 | 110 | ||
109 | return Promise.all([ | 111 | return Promise.all([ |
110 | VideoChangeOwnershipModel.scope(ScopeNames.WITH_ACCOUNTS).count(query), | 112 | VideoChangeOwnershipModel.scope(ScopeNames.WITH_ACCOUNTS).count(query), |
111 | VideoChangeOwnershipModel.scope([ ScopeNames.WITH_ACCOUNTS, ScopeNames.WITH_VIDEO ]).findAll(query) | 113 | VideoChangeOwnershipModel.scope([ ScopeNames.WITH_ACCOUNTS, ScopeNames.WITH_VIDEO ]).findAll<MVideoChangeOwnershipFull>(query) |
112 | ]).then(([ count, rows ]) => ({ total: count, data: rows })) | 114 | ]).then(([ count, rows ]) => ({ total: count, data: rows })) |
113 | } | 115 | } |
114 | 116 | ||
115 | static load (id: number) { | 117 | static load (id: number): Bluebird<MVideoChangeOwnershipFull> { |
116 | return VideoChangeOwnershipModel.scope([ ScopeNames.WITH_ACCOUNTS, ScopeNames.WITH_VIDEO ]) | 118 | return VideoChangeOwnershipModel.scope([ ScopeNames.WITH_ACCOUNTS, ScopeNames.WITH_VIDEO ]) |
117 | .findByPk(id) | 119 | .findByPk(id) |
118 | } | 120 | } |
119 | 121 | ||
120 | toFormattedJSON (): VideoChangeOwnership { | 122 | toFormattedJSON (this: MVideoChangeOwnershipFormattable): VideoChangeOwnership { |
121 | return { | 123 | return { |
122 | id: this.id, | 124 | id: this.id, |
123 | status: this.status, | 125 | status: this.status, |
diff --git a/server/models/video/video-channel.ts b/server/models/video/video-channel.ts index 6241a75a3..05545bd9d 100644 --- a/server/models/video/video-channel.ts +++ b/server/models/video/video-channel.ts | |||
@@ -33,6 +33,15 @@ import { ServerModel } from '../server/server' | |||
33 | import { FindOptions, ModelIndexesOptions, Op } from 'sequelize' | 33 | import { FindOptions, ModelIndexesOptions, Op } from 'sequelize' |
34 | import { AvatarModel } from '../avatar/avatar' | 34 | import { AvatarModel } from '../avatar/avatar' |
35 | import { VideoPlaylistModel } from './video-playlist' | 35 | import { VideoPlaylistModel } from './video-playlist' |
36 | import * as Bluebird from 'bluebird' | ||
37 | import { | ||
38 | MChannelAccountDefault, | ||
39 | MChannelActor, | ||
40 | MChannelActorAccountDefaultVideos, | ||
41 | MChannelAP, | ||
42 | MChannelFormattable, | ||
43 | MChannelSummaryFormattable | ||
44 | } from '../../typings/models/video' | ||
36 | 45 | ||
37 | // FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation | 46 | // FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation |
38 | const indexes: ModelIndexesOptions[] = [ | 47 | const indexes: ModelIndexesOptions[] = [ |
@@ -47,7 +56,7 @@ const indexes: ModelIndexesOptions[] = [ | |||
47 | ] | 56 | ] |
48 | 57 | ||
49 | export enum ScopeNames { | 58 | export enum ScopeNames { |
50 | AVAILABLE_FOR_LIST = 'AVAILABLE_FOR_LIST', | 59 | FOR_API = 'FOR_API', |
51 | WITH_ACCOUNT = 'WITH_ACCOUNT', | 60 | WITH_ACCOUNT = 'WITH_ACCOUNT', |
52 | WITH_ACTOR = 'WITH_ACTOR', | 61 | WITH_ACTOR = 'WITH_ACTOR', |
53 | WITH_VIDEOS = 'WITH_VIDEOS', | 62 | WITH_VIDEOS = 'WITH_VIDEOS', |
@@ -74,10 +83,10 @@ export type SummaryOptions = { | |||
74 | @Scopes(() => ({ | 83 | @Scopes(() => ({ |
75 | [ScopeNames.SUMMARY]: (options: SummaryOptions = {}) => { | 84 | [ScopeNames.SUMMARY]: (options: SummaryOptions = {}) => { |
76 | const base: FindOptions = { | 85 | const base: FindOptions = { |
77 | attributes: [ 'name', 'description', 'id', 'actorId' ], | 86 | attributes: [ 'id', 'name', 'description', 'actorId' ], |
78 | include: [ | 87 | include: [ |
79 | { | 88 | { |
80 | attributes: [ 'preferredUsername', 'url', 'serverId', 'avatarId' ], | 89 | attributes: [ 'id', 'preferredUsername', 'url', 'serverId', 'avatarId' ], |
81 | model: ActorModel.unscoped(), | 90 | model: ActorModel.unscoped(), |
82 | required: true, | 91 | required: true, |
83 | include: [ | 92 | include: [ |
@@ -106,7 +115,7 @@ export type SummaryOptions = { | |||
106 | 115 | ||
107 | return base | 116 | return base |
108 | }, | 117 | }, |
109 | [ScopeNames.AVAILABLE_FOR_LIST]: (options: AvailableForListOptions) => { | 118 | [ScopeNames.FOR_API]: (options: AvailableForListOptions) => { |
110 | // Only list local channels OR channels that are on an instance followed by actorId | 119 | // Only list local channels OR channels that are on an instance followed by actorId |
111 | const inQueryInstanceFollow = buildServerIdsFollowedBy(options.actorId) | 120 | const inQueryInstanceFollow = buildServerIdsFollowedBy(options.actorId) |
112 | 121 | ||
@@ -268,7 +277,7 @@ export class VideoChannelModel extends Model<VideoChannelModel> { | |||
268 | } | 277 | } |
269 | 278 | ||
270 | const scopes = { | 279 | const scopes = { |
271 | method: [ ScopeNames.AVAILABLE_FOR_LIST, { actorId } as AvailableForListOptions ] | 280 | method: [ ScopeNames.FOR_API, { actorId } as AvailableForListOptions ] |
272 | } | 281 | } |
273 | return VideoChannelModel | 282 | return VideoChannelModel |
274 | .scope(scopes) | 283 | .scope(scopes) |
@@ -278,7 +287,7 @@ export class VideoChannelModel extends Model<VideoChannelModel> { | |||
278 | }) | 287 | }) |
279 | } | 288 | } |
280 | 289 | ||
281 | static listLocalsForSitemap (sort: string) { | 290 | static listLocalsForSitemap (sort: string): Bluebird<MChannelActor[]> { |
282 | const query = { | 291 | const query = { |
283 | attributes: [ ], | 292 | attributes: [ ], |
284 | offset: 0, | 293 | offset: 0, |
@@ -331,7 +340,7 @@ export class VideoChannelModel extends Model<VideoChannelModel> { | |||
331 | } | 340 | } |
332 | 341 | ||
333 | const scopes = { | 342 | const scopes = { |
334 | method: [ ScopeNames.AVAILABLE_FOR_LIST, { actorId: options.actorId } as AvailableForListOptions ] | 343 | method: [ ScopeNames.FOR_API, { actorId: options.actorId } as AvailableForListOptions ] |
335 | } | 344 | } |
336 | return VideoChannelModel | 345 | return VideoChannelModel |
337 | .scope(scopes) | 346 | .scope(scopes) |
@@ -369,13 +378,13 @@ export class VideoChannelModel extends Model<VideoChannelModel> { | |||
369 | }) | 378 | }) |
370 | } | 379 | } |
371 | 380 | ||
372 | static loadByIdAndPopulateAccount (id: number) { | 381 | static loadByIdAndPopulateAccount (id: number): Bluebird<MChannelAccountDefault> { |
373 | return VideoChannelModel.unscoped() | 382 | return VideoChannelModel.unscoped() |
374 | .scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT ]) | 383 | .scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT ]) |
375 | .findByPk(id) | 384 | .findByPk(id) |
376 | } | 385 | } |
377 | 386 | ||
378 | static loadByIdAndAccount (id: number, accountId: number) { | 387 | static loadByIdAndAccount (id: number, accountId: number): Bluebird<MChannelAccountDefault> { |
379 | const query = { | 388 | const query = { |
380 | where: { | 389 | where: { |
381 | id, | 390 | id, |
@@ -388,13 +397,13 @@ export class VideoChannelModel extends Model<VideoChannelModel> { | |||
388 | .findOne(query) | 397 | .findOne(query) |
389 | } | 398 | } |
390 | 399 | ||
391 | static loadAndPopulateAccount (id: number) { | 400 | static loadAndPopulateAccount (id: number): Bluebird<MChannelAccountDefault> { |
392 | return VideoChannelModel.unscoped() | 401 | return VideoChannelModel.unscoped() |
393 | .scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT ]) | 402 | .scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT ]) |
394 | .findByPk(id) | 403 | .findByPk(id) |
395 | } | 404 | } |
396 | 405 | ||
397 | static loadByUrlAndPopulateAccount (url: string) { | 406 | static loadByUrlAndPopulateAccount (url: string): Bluebird<MChannelAccountDefault> { |
398 | const query = { | 407 | const query = { |
399 | include: [ | 408 | include: [ |
400 | { | 409 | { |
@@ -420,7 +429,7 @@ export class VideoChannelModel extends Model<VideoChannelModel> { | |||
420 | return VideoChannelModel.loadByNameAndHostAndPopulateAccount(name, host) | 429 | return VideoChannelModel.loadByNameAndHostAndPopulateAccount(name, host) |
421 | } | 430 | } |
422 | 431 | ||
423 | static loadLocalByNameAndPopulateAccount (name: string) { | 432 | static loadLocalByNameAndPopulateAccount (name: string): Bluebird<MChannelAccountDefault> { |
424 | const query = { | 433 | const query = { |
425 | include: [ | 434 | include: [ |
426 | { | 435 | { |
@@ -439,7 +448,7 @@ export class VideoChannelModel extends Model<VideoChannelModel> { | |||
439 | .findOne(query) | 448 | .findOne(query) |
440 | } | 449 | } |
441 | 450 | ||
442 | static loadByNameAndHostAndPopulateAccount (name: string, host: string) { | 451 | static loadByNameAndHostAndPopulateAccount (name: string, host: string): Bluebird<MChannelAccountDefault> { |
443 | const query = { | 452 | const query = { |
444 | include: [ | 453 | include: [ |
445 | { | 454 | { |
@@ -464,7 +473,7 @@ export class VideoChannelModel extends Model<VideoChannelModel> { | |||
464 | .findOne(query) | 473 | .findOne(query) |
465 | } | 474 | } |
466 | 475 | ||
467 | static loadAndPopulateAccountAndVideos (id: number) { | 476 | static loadAndPopulateAccountAndVideos (id: number): Bluebird<MChannelActorAccountDefaultVideos> { |
468 | const options = { | 477 | const options = { |
469 | include: [ | 478 | include: [ |
470 | VideoModel | 479 | VideoModel |
@@ -476,7 +485,20 @@ export class VideoChannelModel extends Model<VideoChannelModel> { | |||
476 | .findByPk(id, options) | 485 | .findByPk(id, options) |
477 | } | 486 | } |
478 | 487 | ||
479 | toFormattedJSON (): VideoChannel { | 488 | toFormattedSummaryJSON (this: MChannelSummaryFormattable): VideoChannelSummary { |
489 | const actor = this.Actor.toFormattedSummaryJSON() | ||
490 | |||
491 | return { | ||
492 | id: this.id, | ||
493 | name: actor.name, | ||
494 | displayName: this.getDisplayName(), | ||
495 | url: actor.url, | ||
496 | host: actor.host, | ||
497 | avatar: actor.avatar | ||
498 | } | ||
499 | } | ||
500 | |||
501 | toFormattedJSON (this: MChannelFormattable): VideoChannel { | ||
480 | const actor = this.Actor.toFormattedJSON() | 502 | const actor = this.Actor.toFormattedJSON() |
481 | const videoChannel = { | 503 | const videoChannel = { |
482 | id: this.id, | 504 | id: this.id, |
@@ -494,21 +516,8 @@ export class VideoChannelModel extends Model<VideoChannelModel> { | |||
494 | return Object.assign(actor, videoChannel) | 516 | return Object.assign(actor, videoChannel) |
495 | } | 517 | } |
496 | 518 | ||
497 | toFormattedSummaryJSON (): VideoChannelSummary { | 519 | toActivityPubObject (this: MChannelAP): ActivityPubActor { |
498 | const actor = this.Actor.toFormattedJSON() | 520 | const obj = this.Actor.toActivityPubObject(this.name) |
499 | |||
500 | return { | ||
501 | id: this.id, | ||
502 | name: actor.name, | ||
503 | displayName: this.getDisplayName(), | ||
504 | url: actor.url, | ||
505 | host: actor.host, | ||
506 | avatar: actor.avatar | ||
507 | } | ||
508 | } | ||
509 | |||
510 | toActivityPubObject (): ActivityPubActor { | ||
511 | const obj = this.Actor.toActivityPubObject(this.name, 'VideoChannel') | ||
512 | 521 | ||
513 | return Object.assign(obj, { | 522 | return Object.assign(obj, { |
514 | summary: this.description, | 523 | summary: this.description, |
diff --git a/server/models/video/video-comment.ts b/server/models/video/video-comment.ts index 58b75510d..2e4220434 100644 --- a/server/models/video/video-comment.ts +++ b/server/models/video/video-comment.ts | |||
@@ -1,36 +1,32 @@ | |||
1 | import { | 1 | import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' |
2 | AllowNull, | ||
3 | BeforeDestroy, | ||
4 | BelongsTo, | ||
5 | Column, | ||
6 | CreatedAt, | ||
7 | DataType, | ||
8 | ForeignKey, | ||
9 | Is, | ||
10 | Model, | ||
11 | Scopes, | ||
12 | Table, | ||
13 | UpdatedAt | ||
14 | } from 'sequelize-typescript' | ||
15 | import { ActivityTagObject } from '../../../shared/models/activitypub/objects/common-objects' | 2 | import { ActivityTagObject } from '../../../shared/models/activitypub/objects/common-objects' |
16 | import { VideoCommentObject } from '../../../shared/models/activitypub/objects/video-comment-object' | 3 | import { VideoCommentObject } from '../../../shared/models/activitypub/objects/video-comment-object' |
17 | import { VideoComment } from '../../../shared/models/videos/video-comment.model' | 4 | import { VideoComment } from '../../../shared/models/videos/video-comment.model' |
18 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' | 5 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' |
19 | import { CONSTRAINTS_FIELDS, WEBSERVER } from '../../initializers/constants' | 6 | import { CONSTRAINTS_FIELDS, WEBSERVER } from '../../initializers/constants' |
20 | import { sendDeleteVideoComment } from '../../lib/activitypub/send' | ||
21 | import { AccountModel } from '../account/account' | 7 | import { AccountModel } from '../account/account' |
22 | import { ActorModel } from '../activitypub/actor' | 8 | import { ActorModel } from '../activitypub/actor' |
23 | import { AvatarModel } from '../avatar/avatar' | ||
24 | import { ServerModel } from '../server/server' | ||
25 | import { buildBlockedAccountSQL, buildLocalAccountIdsIn, getSort, throwIfNotValid } from '../utils' | 9 | import { buildBlockedAccountSQL, buildLocalAccountIdsIn, getSort, throwIfNotValid } from '../utils' |
26 | import { VideoModel } from './video' | 10 | import { VideoModel } from './video' |
27 | import { VideoChannelModel } from './video-channel' | 11 | import { VideoChannelModel } from './video-channel' |
28 | import { getServerActor } from '../../helpers/utils' | 12 | import { getServerActor } from '../../helpers/utils' |
29 | import { UserModel } from '../account/user' | ||
30 | import { actorNameAlphabet } from '../../helpers/custom-validators/activitypub/actor' | 13 | import { actorNameAlphabet } from '../../helpers/custom-validators/activitypub/actor' |
31 | import { regexpCapture } from '../../helpers/regexp' | 14 | import { regexpCapture } from '../../helpers/regexp' |
32 | import { uniq } from 'lodash' | 15 | import { uniq } from 'lodash' |
33 | import { FindOptions, literal, Op, Order, ScopeOptions, Sequelize, Transaction } from 'sequelize' | 16 | import { FindOptions, Op, Order, ScopeOptions, Sequelize, Transaction } from 'sequelize' |
17 | import * as Bluebird from 'bluebird' | ||
18 | import { | ||
19 | MComment, | ||
20 | MCommentAP, | ||
21 | MCommentFormattable, | ||
22 | MCommentId, | ||
23 | MCommentOwner, | ||
24 | MCommentOwnerReplyVideoLight, | ||
25 | MCommentOwnerVideo, | ||
26 | MCommentOwnerVideoFeed, | ||
27 | MCommentOwnerVideoReply | ||
28 | } from '../../typings/models/video' | ||
29 | import { MUserAccountId } from '@server/typings/models' | ||
34 | 30 | ||
35 | enum ScopeNames { | 31 | enum ScopeNames { |
36 | WITH_ACCOUNT = 'WITH_ACCOUNT', | 32 | WITH_ACCOUNT = 'WITH_ACCOUNT', |
@@ -68,22 +64,7 @@ enum ScopeNames { | |||
68 | [ScopeNames.WITH_ACCOUNT]: { | 64 | [ScopeNames.WITH_ACCOUNT]: { |
69 | include: [ | 65 | include: [ |
70 | { | 66 | { |
71 | model: AccountModel, | 67 | model: AccountModel |
72 | include: [ | ||
73 | { | ||
74 | model: ActorModel, | ||
75 | include: [ | ||
76 | { | ||
77 | model: ServerModel, | ||
78 | required: false | ||
79 | }, | ||
80 | { | ||
81 | model: AvatarModel, | ||
82 | required: false | ||
83 | } | ||
84 | ] | ||
85 | } | ||
86 | ] | ||
87 | } | 68 | } |
88 | ] | 69 | ] |
89 | }, | 70 | }, |
@@ -102,22 +83,12 @@ enum ScopeNames { | |||
102 | required: true, | 83 | required: true, |
103 | include: [ | 84 | include: [ |
104 | { | 85 | { |
105 | model: VideoChannelModel.unscoped(), | 86 | model: VideoChannelModel, |
106 | required: true, | 87 | required: true, |
107 | include: [ | 88 | include: [ |
108 | { | 89 | { |
109 | model: ActorModel, | ||
110 | required: true | ||
111 | }, | ||
112 | { | ||
113 | model: AccountModel, | 90 | model: AccountModel, |
114 | required: true, | 91 | required: true |
115 | include: [ | ||
116 | { | ||
117 | model: ActorModel, | ||
118 | required: true | ||
119 | } | ||
120 | ] | ||
121 | } | 92 | } |
122 | ] | 93 | ] |
123 | } | 94 | } |
@@ -212,7 +183,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> { | |||
212 | }) | 183 | }) |
213 | Account: AccountModel | 184 | Account: AccountModel |
214 | 185 | ||
215 | static loadById (id: number, t?: Transaction) { | 186 | static loadById (id: number, t?: Transaction): Bluebird<MComment> { |
216 | const query: FindOptions = { | 187 | const query: FindOptions = { |
217 | where: { | 188 | where: { |
218 | id | 189 | id |
@@ -224,7 +195,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> { | |||
224 | return VideoCommentModel.findOne(query) | 195 | return VideoCommentModel.findOne(query) |
225 | } | 196 | } |
226 | 197 | ||
227 | static loadByIdAndPopulateVideoAndAccountAndReply (id: number, t?: Transaction) { | 198 | static loadByIdAndPopulateVideoAndAccountAndReply (id: number, t?: Transaction): Bluebird<MCommentOwnerVideoReply> { |
228 | const query: FindOptions = { | 199 | const query: FindOptions = { |
229 | where: { | 200 | where: { |
230 | id | 201 | id |
@@ -238,7 +209,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> { | |||
238 | .findOne(query) | 209 | .findOne(query) |
239 | } | 210 | } |
240 | 211 | ||
241 | static loadByUrlAndPopulateAccountAndVideo (url: string, t?: Transaction) { | 212 | static loadByUrlAndPopulateAccountAndVideo (url: string, t?: Transaction): Bluebird<MCommentOwnerVideo> { |
242 | const query: FindOptions = { | 213 | const query: FindOptions = { |
243 | where: { | 214 | where: { |
244 | url | 215 | url |
@@ -250,7 +221,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> { | |||
250 | return VideoCommentModel.scope([ ScopeNames.WITH_ACCOUNT, ScopeNames.WITH_VIDEO ]).findOne(query) | 221 | return VideoCommentModel.scope([ ScopeNames.WITH_ACCOUNT, ScopeNames.WITH_VIDEO ]).findOne(query) |
251 | } | 222 | } |
252 | 223 | ||
253 | static loadByUrlAndPopulateReplyAndVideoUrlAndAccount (url: string, t?: Transaction) { | 224 | static loadByUrlAndPopulateReplyAndVideoUrlAndAccount (url: string, t?: Transaction): Bluebird<MCommentOwnerReplyVideoLight> { |
254 | const query: FindOptions = { | 225 | const query: FindOptions = { |
255 | where: { | 226 | where: { |
256 | url | 227 | url |
@@ -273,7 +244,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> { | |||
273 | start: number, | 244 | start: number, |
274 | count: number, | 245 | count: number, |
275 | sort: string, | 246 | sort: string, |
276 | user?: UserModel | 247 | user?: MUserAccountId |
277 | }) { | 248 | }) { |
278 | const { videoId, start, count, sort, user } = parameters | 249 | const { videoId, start, count, sort, user } = parameters |
279 | 250 | ||
@@ -314,7 +285,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> { | |||
314 | static async listThreadCommentsForApi (parameters: { | 285 | static async listThreadCommentsForApi (parameters: { |
315 | videoId: number, | 286 | videoId: number, |
316 | threadId: number, | 287 | threadId: number, |
317 | user?: UserModel | 288 | user?: MUserAccountId |
318 | }) { | 289 | }) { |
319 | const { videoId, threadId, user } = parameters | 290 | const { videoId, threadId, user } = parameters |
320 | 291 | ||
@@ -353,7 +324,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> { | |||
353 | }) | 324 | }) |
354 | } | 325 | } |
355 | 326 | ||
356 | static listThreadParentComments (comment: VideoCommentModel, t: Transaction, order: 'ASC' | 'DESC' = 'ASC') { | 327 | static listThreadParentComments (comment: MCommentId, t: Transaction, order: 'ASC' | 'DESC' = 'ASC'): Bluebird<MCommentOwner[]> { |
357 | const query = { | 328 | const query = { |
358 | order: [ [ 'createdAt', order ] ] as Order, | 329 | order: [ [ 'createdAt', order ] ] as Order, |
359 | where: { | 330 | where: { |
@@ -389,10 +360,10 @@ export class VideoCommentModel extends Model<VideoCommentModel> { | |||
389 | transaction: t | 360 | transaction: t |
390 | } | 361 | } |
391 | 362 | ||
392 | return VideoCommentModel.findAndCountAll(query) | 363 | return VideoCommentModel.findAndCountAll<MComment>(query) |
393 | } | 364 | } |
394 | 365 | ||
395 | static listForFeed (start: number, count: number, videoId?: number) { | 366 | static listForFeed (start: number, count: number, videoId?: number): Bluebird<MCommentOwnerVideoFeed[]> { |
396 | const query = { | 367 | const query = { |
397 | order: [ [ 'createdAt', 'DESC' ] ] as Order, | 368 | order: [ [ 'createdAt', 'DESC' ] ] as Order, |
398 | offset: start, | 369 | offset: start, |
@@ -506,7 +477,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> { | |||
506 | return uniq(result) | 477 | return uniq(result) |
507 | } | 478 | } |
508 | 479 | ||
509 | toFormattedJSON () { | 480 | toFormattedJSON (this: MCommentFormattable) { |
510 | return { | 481 | return { |
511 | id: this.id, | 482 | id: this.id, |
512 | url: this.url, | 483 | url: this.url, |
@@ -521,7 +492,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> { | |||
521 | } as VideoComment | 492 | } as VideoComment |
522 | } | 493 | } |
523 | 494 | ||
524 | toActivityPubObject (threadParentComments: VideoCommentModel[]): VideoCommentObject { | 495 | toActivityPubObject (this: MCommentAP, threadParentComments: MCommentOwner[]): VideoCommentObject { |
525 | let inReplyTo: string | 496 | let inReplyTo: string |
526 | // New thread, so in AS we reply to the video | 497 | // New thread, so in AS we reply to the video |
527 | if (this.inReplyToCommentId === null) { | 498 | if (this.inReplyToCommentId === null) { |
diff --git a/server/models/video/video-file.ts b/server/models/video/video-file.ts index 05c490759..6304f741c 100644 --- a/server/models/video/video-file.ts +++ b/server/models/video/video-file.ts | |||
@@ -25,6 +25,7 @@ import { VideoRedundancyModel } from '../redundancy/video-redundancy' | |||
25 | import { VideoStreamingPlaylistModel } from './video-streaming-playlist' | 25 | import { VideoStreamingPlaylistModel } from './video-streaming-playlist' |
26 | import { FindOptions, QueryTypes, Transaction } from 'sequelize' | 26 | import { FindOptions, QueryTypes, Transaction } from 'sequelize' |
27 | import { MIMETYPES } from '../../initializers/constants' | 27 | import { MIMETYPES } from '../../initializers/constants' |
28 | import { MVideoFile } from '@server/typings/models' | ||
28 | 29 | ||
29 | @Table({ | 30 | @Table({ |
30 | tableName: 'videoFile', | 31 | tableName: 'videoFile', |
@@ -166,7 +167,7 @@ export class VideoFileModel extends Model<VideoFileModel> { | |||
166 | return !!MIMETYPES.AUDIO.EXT_MIMETYPE[this.extname] | 167 | return !!MIMETYPES.AUDIO.EXT_MIMETYPE[this.extname] |
167 | } | 168 | } |
168 | 169 | ||
169 | hasSameUniqueKeysThan (other: VideoFileModel) { | 170 | hasSameUniqueKeysThan (other: MVideoFile) { |
170 | return this.fps === other.fps && | 171 | return this.fps === other.fps && |
171 | this.resolution === other.resolution && | 172 | this.resolution === other.resolution && |
172 | this.videoId === other.videoId | 173 | this.videoId === other.videoId |
diff --git a/server/models/video/video-format-utils.ts b/server/models/video/video-format-utils.ts index 284539def..2987aa780 100644 --- a/server/models/video/video-format-utils.ts +++ b/server/models/video/video-format-utils.ts | |||
@@ -1,6 +1,5 @@ | |||
1 | import { Video, VideoDetails, VideoFile } from '../../../shared/models/videos' | 1 | import { Video, VideoDetails, VideoFile } from '../../../shared/models/videos' |
2 | import { VideoModel } from './video' | 2 | import { VideoModel } from './video' |
3 | import { VideoFileModel } from './video-file' | ||
4 | import { | 3 | import { |
5 | ActivityPlaylistInfohashesObject, | 4 | ActivityPlaylistInfohashesObject, |
6 | ActivityPlaylistSegmentHashesObject, | 5 | ActivityPlaylistSegmentHashesObject, |
@@ -17,7 +16,9 @@ import { | |||
17 | } from '../../lib/activitypub' | 16 | } from '../../lib/activitypub' |
18 | import { isArray } from '../../helpers/custom-validators/misc' | 17 | import { isArray } from '../../helpers/custom-validators/misc' |
19 | import { VideoStreamingPlaylist } from '../../../shared/models/videos/video-streaming-playlist.model' | 18 | import { VideoStreamingPlaylist } from '../../../shared/models/videos/video-streaming-playlist.model' |
20 | import { VideoStreamingPlaylistModel } from './video-streaming-playlist' | 19 | import { MStreamingPlaylistRedundanciesOpt, MVideo, MVideoAP, MVideoFormattable, MVideoFormattableDetails } from '../../typings/models' |
20 | import { MStreamingPlaylistRedundancies } from '../../typings/models/video/video-streaming-playlist' | ||
21 | import { MVideoFileRedundanciesOpt } from '../../typings/models/video/video-file' | ||
21 | 22 | ||
22 | export type VideoFormattingJSONOptions = { | 23 | export type VideoFormattingJSONOptions = { |
23 | completeDescription?: boolean | 24 | completeDescription?: boolean |
@@ -28,7 +29,7 @@ export type VideoFormattingJSONOptions = { | |||
28 | blacklistInfo?: boolean | 29 | blacklistInfo?: boolean |
29 | } | 30 | } |
30 | } | 31 | } |
31 | function videoModelToFormattedJSON (video: VideoModel, options?: VideoFormattingJSONOptions): Video { | 32 | function videoModelToFormattedJSON (video: MVideoFormattable, options?: VideoFormattingJSONOptions): Video { |
32 | const userHistory = isArray(video.UserVideoHistories) ? video.UserVideoHistories[0] : undefined | 33 | const userHistory = isArray(video.UserVideoHistories) ? video.UserVideoHistories[0] : undefined |
33 | 34 | ||
34 | const videoObject: Video = { | 35 | const videoObject: Video = { |
@@ -102,7 +103,7 @@ function videoModelToFormattedJSON (video: VideoModel, options?: VideoFormatting | |||
102 | return videoObject | 103 | return videoObject |
103 | } | 104 | } |
104 | 105 | ||
105 | function videoModelToFormattedDetailsJSON (video: VideoModel): VideoDetails { | 106 | function videoModelToFormattedDetailsJSON (video: MVideoFormattableDetails): VideoDetails { |
106 | const formattedJson = video.toFormattedJSON({ | 107 | const formattedJson = video.toFormattedJSON({ |
107 | additionalAttributes: { | 108 | additionalAttributes: { |
108 | scheduledUpdate: true, | 109 | scheduledUpdate: true, |
@@ -114,7 +115,7 @@ function videoModelToFormattedDetailsJSON (video: VideoModel): VideoDetails { | |||
114 | 115 | ||
115 | const tags = video.Tags ? video.Tags.map(t => t.name) : [] | 116 | const tags = video.Tags ? video.Tags.map(t => t.name) : [] |
116 | 117 | ||
117 | const streamingPlaylists = streamingPlaylistsModelToFormattedJSON(video, video.VideoStreamingPlaylists) | 118 | const streamingPlaylists = streamingPlaylistsModelToFormattedJSON(video.VideoStreamingPlaylists) |
118 | 119 | ||
119 | const detailsJson = { | 120 | const detailsJson = { |
120 | support: video.support, | 121 | support: video.support, |
@@ -142,7 +143,7 @@ function videoModelToFormattedDetailsJSON (video: VideoModel): VideoDetails { | |||
142 | return Object.assign(formattedJson, detailsJson) | 143 | return Object.assign(formattedJson, detailsJson) |
143 | } | 144 | } |
144 | 145 | ||
145 | function streamingPlaylistsModelToFormattedJSON (video: VideoModel, playlists: VideoStreamingPlaylistModel[]): VideoStreamingPlaylist[] { | 146 | function streamingPlaylistsModelToFormattedJSON (playlists: MStreamingPlaylistRedundanciesOpt[]): VideoStreamingPlaylist[] { |
146 | if (isArray(playlists) === false) return [] | 147 | if (isArray(playlists) === false) return [] |
147 | 148 | ||
148 | return playlists | 149 | return playlists |
@@ -161,7 +162,7 @@ function streamingPlaylistsModelToFormattedJSON (video: VideoModel, playlists: V | |||
161 | }) | 162 | }) |
162 | } | 163 | } |
163 | 164 | ||
164 | function videoFilesModelToFormattedJSON (video: VideoModel, videoFiles: VideoFileModel[]): VideoFile[] { | 165 | function videoFilesModelToFormattedJSON (video: MVideo, videoFiles: MVideoFileRedundanciesOpt[]): VideoFile[] { |
165 | const { baseUrlHttp, baseUrlWs } = video.getBaseUrls() | 166 | const { baseUrlHttp, baseUrlWs } = video.getBaseUrls() |
166 | 167 | ||
167 | return videoFiles | 168 | return videoFiles |
@@ -189,7 +190,7 @@ function videoFilesModelToFormattedJSON (video: VideoModel, videoFiles: VideoFil | |||
189 | }) | 190 | }) |
190 | } | 191 | } |
191 | 192 | ||
192 | function videoModelToActivityPubObject (video: VideoModel): VideoTorrentObject { | 193 | function videoModelToActivityPubObject (video: MVideoAP): VideoTorrentObject { |
193 | const { baseUrlHttp, baseUrlWs } = video.getBaseUrls() | 194 | const { baseUrlHttp, baseUrlWs } = video.getBaseUrls() |
194 | if (!video.Tags) video.Tags = [] | 195 | if (!video.Tags) video.Tags = [] |
195 | 196 | ||
diff --git a/server/models/video/video-import.ts b/server/models/video/video-import.ts index 480a671c8..af5314ce9 100644 --- a/server/models/video/video-import.ts +++ b/server/models/video/video-import.ts | |||
@@ -20,6 +20,8 @@ import { isVideoImportStateValid, isVideoImportTargetUrlValid } from '../../help | |||
20 | import { VideoImport, VideoImportState } from '../../../shared' | 20 | import { VideoImport, VideoImportState } from '../../../shared' |
21 | import { isVideoMagnetUriValid } from '../../helpers/custom-validators/videos' | 21 | import { isVideoMagnetUriValid } from '../../helpers/custom-validators/videos' |
22 | import { UserModel } from '../account/user' | 22 | import { UserModel } from '../account/user' |
23 | import * as Bluebird from 'bluebird' | ||
24 | import { MVideoImportDefault, MVideoImportFormattable } from '@server/typings/models/video/video-import' | ||
23 | 25 | ||
24 | @DefaultScope(() => ({ | 26 | @DefaultScope(() => ({ |
25 | include: [ | 27 | include: [ |
@@ -28,7 +30,11 @@ import { UserModel } from '../account/user' | |||
28 | required: true | 30 | required: true |
29 | }, | 31 | }, |
30 | { | 32 | { |
31 | model: VideoModel.scope([ VideoModelScopeNames.WITH_ACCOUNT_DETAILS, VideoModelScopeNames.WITH_TAGS]), | 33 | model: VideoModel.scope([ |
34 | VideoModelScopeNames.WITH_ACCOUNT_DETAILS, | ||
35 | VideoModelScopeNames.WITH_TAGS, | ||
36 | VideoModelScopeNames.WITH_THUMBNAILS | ||
37 | ]), | ||
32 | required: false | 38 | required: false |
33 | } | 39 | } |
34 | ] | 40 | ] |
@@ -114,7 +120,7 @@ export class VideoImportModel extends Model<VideoImportModel> { | |||
114 | return undefined | 120 | return undefined |
115 | } | 121 | } |
116 | 122 | ||
117 | static loadAndPopulateVideo (id: number) { | 123 | static loadAndPopulateVideo (id: number): Bluebird<MVideoImportDefault> { |
118 | return VideoImportModel.findByPk(id) | 124 | return VideoImportModel.findByPk(id) |
119 | } | 125 | } |
120 | 126 | ||
@@ -135,7 +141,7 @@ export class VideoImportModel extends Model<VideoImportModel> { | |||
135 | } | 141 | } |
136 | } | 142 | } |
137 | 143 | ||
138 | return VideoImportModel.findAndCountAll(query) | 144 | return VideoImportModel.findAndCountAll<MVideoImportDefault>(query) |
139 | .then(({ rows, count }) => { | 145 | .then(({ rows, count }) => { |
140 | return { | 146 | return { |
141 | data: rows, | 147 | data: rows, |
@@ -148,7 +154,7 @@ export class VideoImportModel extends Model<VideoImportModel> { | |||
148 | return this.targetUrl || this.magnetUri || this.torrentName | 154 | return this.targetUrl || this.magnetUri || this.torrentName |
149 | } | 155 | } |
150 | 156 | ||
151 | toFormattedJSON (): VideoImport { | 157 | toFormattedJSON (this: MVideoImportFormattable): VideoImport { |
152 | const videoFormatOptions = { | 158 | const videoFormatOptions = { |
153 | completeDescription: true, | 159 | completeDescription: true, |
154 | additionalAttributes: { state: true, waitTranscoding: true, scheduledUpdate: true } | 160 | additionalAttributes: { state: true, waitTranscoding: true, scheduledUpdate: true } |
diff --git a/server/models/video/video-playlist-element.ts b/server/models/video/video-playlist-element.ts index dd7653533..a28021313 100644 --- a/server/models/video/video-playlist-element.ts +++ b/server/models/video/video-playlist-element.ts | |||
@@ -21,10 +21,18 @@ import { CONSTRAINTS_FIELDS } from '../../initializers/constants' | |||
21 | import { PlaylistElementObject } from '../../../shared/models/activitypub/objects/playlist-element-object' | 21 | import { PlaylistElementObject } from '../../../shared/models/activitypub/objects/playlist-element-object' |
22 | import * as validator from 'validator' | 22 | import * as validator from 'validator' |
23 | import { AggregateOptions, Op, ScopeOptions, Sequelize, Transaction } from 'sequelize' | 23 | import { AggregateOptions, Op, ScopeOptions, Sequelize, Transaction } from 'sequelize' |
24 | import { UserModel } from '../account/user' | ||
25 | import { VideoPlaylistElement, VideoPlaylistElementType } from '../../../shared/models/videos/playlist/video-playlist-element.model' | 24 | import { VideoPlaylistElement, VideoPlaylistElementType } from '../../../shared/models/videos/playlist/video-playlist-element.model' |
26 | import { AccountModel } from '../account/account' | 25 | import { AccountModel } from '../account/account' |
27 | import { VideoPrivacy } from '../../../shared/models/videos' | 26 | import { VideoPrivacy } from '../../../shared/models/videos' |
27 | import * as Bluebird from 'bluebird' | ||
28 | import { | ||
29 | MVideoPlaylistElement, | ||
30 | MVideoPlaylistElementAP, | ||
31 | MVideoPlaylistElementFormattable, | ||
32 | MVideoPlaylistElementVideoUrlPlaylistPrivacy, | ||
33 | MVideoPlaylistVideoThumbnail | ||
34 | } from '@server/typings/models/video/video-playlist-element' | ||
35 | import { MUserAccountId } from '@server/typings/models' | ||
28 | 36 | ||
29 | @Table({ | 37 | @Table({ |
30 | tableName: 'videoPlaylistElement', | 38 | tableName: 'videoPlaylistElement', |
@@ -116,7 +124,7 @@ export class VideoPlaylistElementModel extends Model<VideoPlaylistElementModel> | |||
116 | count: number, | 124 | count: number, |
117 | videoPlaylistId: number, | 125 | videoPlaylistId: number, |
118 | serverAccount: AccountModel, | 126 | serverAccount: AccountModel, |
119 | user?: UserModel | 127 | user?: MUserAccountId |
120 | }) { | 128 | }) { |
121 | const accountIds = [ options.serverAccount.id ] | 129 | const accountIds = [ options.serverAccount.id ] |
122 | const videoScope: (ScopeOptions | string)[] = [ | 130 | const videoScope: (ScopeOptions | string)[] = [ |
@@ -162,7 +170,7 @@ export class VideoPlaylistElementModel extends Model<VideoPlaylistElementModel> | |||
162 | ]).then(([ total, data ]) => ({ total, data })) | 170 | ]).then(([ total, data ]) => ({ total, data })) |
163 | } | 171 | } |
164 | 172 | ||
165 | static loadByPlaylistAndVideo (videoPlaylistId: number, videoId: number) { | 173 | static loadByPlaylistAndVideo (videoPlaylistId: number, videoId: number): Bluebird<MVideoPlaylistElement> { |
166 | const query = { | 174 | const query = { |
167 | where: { | 175 | where: { |
168 | videoPlaylistId, | 176 | videoPlaylistId, |
@@ -173,11 +181,14 @@ export class VideoPlaylistElementModel extends Model<VideoPlaylistElementModel> | |||
173 | return VideoPlaylistElementModel.findOne(query) | 181 | return VideoPlaylistElementModel.findOne(query) |
174 | } | 182 | } |
175 | 183 | ||
176 | static loadById (playlistElementId: number) { | 184 | static loadById (playlistElementId: number): Bluebird<MVideoPlaylistElement> { |
177 | return VideoPlaylistElementModel.findByPk(playlistElementId) | 185 | return VideoPlaylistElementModel.findByPk(playlistElementId) |
178 | } | 186 | } |
179 | 187 | ||
180 | static loadByPlaylistAndVideoForAP (playlistId: number | string, videoId: number | string) { | 188 | static loadByPlaylistAndVideoForAP ( |
189 | playlistId: number | string, | ||
190 | videoId: number | string | ||
191 | ): Bluebird<MVideoPlaylistElementVideoUrlPlaylistPrivacy> { | ||
181 | const playlistWhere = validator.isUUID('' + playlistId) ? { uuid: playlistId } : { id: playlistId } | 192 | const playlistWhere = validator.isUUID('' + playlistId) ? { uuid: playlistId } : { id: playlistId } |
182 | const videoWhere = validator.isUUID('' + videoId) ? { uuid: videoId } : { id: videoId } | 193 | const videoWhere = validator.isUUID('' + videoId) ? { uuid: videoId } : { id: videoId } |
183 | 194 | ||
@@ -218,7 +229,7 @@ export class VideoPlaylistElementModel extends Model<VideoPlaylistElementModel> | |||
218 | }) | 229 | }) |
219 | } | 230 | } |
220 | 231 | ||
221 | static loadFirstElementWithVideoThumbnail (videoPlaylistId: number) { | 232 | static loadFirstElementWithVideoThumbnail (videoPlaylistId: number): Bluebird<MVideoPlaylistVideoThumbnail> { |
222 | const query = { | 233 | const query = { |
223 | order: getSort('position'), | 234 | order: getSort('position'), |
224 | where: { | 235 | where: { |
@@ -290,7 +301,7 @@ export class VideoPlaylistElementModel extends Model<VideoPlaylistElementModel> | |||
290 | return VideoPlaylistElementModel.increment({ position: by }, query) | 301 | return VideoPlaylistElementModel.increment({ position: by }, query) |
291 | } | 302 | } |
292 | 303 | ||
293 | getType (displayNSFW?: boolean, accountId?: number) { | 304 | getType (this: MVideoPlaylistElementFormattable, displayNSFW?: boolean, accountId?: number) { |
294 | const video = this.Video | 305 | const video = this.Video |
295 | 306 | ||
296 | if (!video) return VideoPlaylistElementType.DELETED | 307 | if (!video) return VideoPlaylistElementType.DELETED |
@@ -306,14 +317,17 @@ export class VideoPlaylistElementModel extends Model<VideoPlaylistElementModel> | |||
306 | return VideoPlaylistElementType.REGULAR | 317 | return VideoPlaylistElementType.REGULAR |
307 | } | 318 | } |
308 | 319 | ||
309 | getVideoElement (displayNSFW?: boolean, accountId?: number) { | 320 | getVideoElement (this: MVideoPlaylistElementFormattable, displayNSFW?: boolean, accountId?: number) { |
310 | if (!this.Video) return null | 321 | if (!this.Video) return null |
311 | if (this.getType(displayNSFW, accountId) !== VideoPlaylistElementType.REGULAR) return null | 322 | if (this.getType(displayNSFW, accountId) !== VideoPlaylistElementType.REGULAR) return null |
312 | 323 | ||
313 | return this.Video.toFormattedJSON() | 324 | return this.Video.toFormattedJSON() |
314 | } | 325 | } |
315 | 326 | ||
316 | toFormattedJSON (options: { displayNSFW?: boolean, accountId?: number } = {}): VideoPlaylistElement { | 327 | toFormattedJSON ( |
328 | this: MVideoPlaylistElementFormattable, | ||
329 | options: { displayNSFW?: boolean, accountId?: number } = {} | ||
330 | ): VideoPlaylistElement { | ||
317 | return { | 331 | return { |
318 | id: this.id, | 332 | id: this.id, |
319 | position: this.position, | 333 | position: this.position, |
@@ -326,7 +340,7 @@ export class VideoPlaylistElementModel extends Model<VideoPlaylistElementModel> | |||
326 | } | 340 | } |
327 | } | 341 | } |
328 | 342 | ||
329 | toActivityPubObject (): PlaylistElementObject { | 343 | toActivityPubObject (this: MVideoPlaylistElementAP): PlaylistElementObject { |
330 | const base: PlaylistElementObject = { | 344 | const base: PlaylistElementObject = { |
331 | id: this.url, | 345 | id: this.url, |
332 | type: 'PlaylistElement', | 346 | type: 'PlaylistElement', |
diff --git a/server/models/video/video-playlist.ts b/server/models/video/video-playlist.ts index c8e97c491..278d80ac0 100644 --- a/server/models/video/video-playlist.ts +++ b/server/models/video/video-playlist.ts | |||
@@ -43,6 +43,15 @@ import { VideoPlaylistType } from '../../../shared/models/videos/playlist/video- | |||
43 | import { ThumbnailModel } from './thumbnail' | 43 | import { ThumbnailModel } from './thumbnail' |
44 | import { ActivityIconObject } from '../../../shared/models/activitypub/objects' | 44 | import { ActivityIconObject } from '../../../shared/models/activitypub/objects' |
45 | import { FindOptions, literal, Op, ScopeOptions, Transaction, WhereOptions } from 'sequelize' | 45 | import { FindOptions, literal, Op, ScopeOptions, Transaction, WhereOptions } from 'sequelize' |
46 | import * as Bluebird from 'bluebird' | ||
47 | import { | ||
48 | MVideoPlaylistAccountThumbnail, MVideoPlaylistAP, | ||
49 | MVideoPlaylistFormattable, | ||
50 | MVideoPlaylistFull, | ||
51 | MVideoPlaylistFullSummary, | ||
52 | MVideoPlaylistIdWithElements | ||
53 | } from '../../typings/models/video/video-playlist' | ||
54 | import { MThumbnail } from '../../typings/models/video/thumbnail' | ||
46 | 55 | ||
47 | enum ScopeNames { | 56 | enum ScopeNames { |
48 | AVAILABLE_FOR_LIST = 'AVAILABLE_FOR_LIST', | 57 | AVAILABLE_FOR_LIST = 'AVAILABLE_FOR_LIST', |
@@ -332,7 +341,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> { | |||
332 | }) | 341 | }) |
333 | } | 342 | } |
334 | 343 | ||
335 | static listPlaylistIdsOf (accountId: number, videoIds: number[]) { | 344 | static listPlaylistIdsOf (accountId: number, videoIds: number[]): Bluebird<MVideoPlaylistIdWithElements[]> { |
336 | const query = { | 345 | const query = { |
337 | attributes: [ 'id' ], | 346 | attributes: [ 'id' ], |
338 | where: { | 347 | where: { |
@@ -368,7 +377,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> { | |||
368 | .then(e => !!e) | 377 | .then(e => !!e) |
369 | } | 378 | } |
370 | 379 | ||
371 | static loadWithAccountAndChannelSummary (id: number | string, transaction: Transaction) { | 380 | static loadWithAccountAndChannelSummary (id: number | string, transaction: Transaction): Bluebird<MVideoPlaylistFullSummary> { |
372 | const where = buildWhereIdOrUUID(id) | 381 | const where = buildWhereIdOrUUID(id) |
373 | 382 | ||
374 | const query = { | 383 | const query = { |
@@ -381,7 +390,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> { | |||
381 | .findOne(query) | 390 | .findOne(query) |
382 | } | 391 | } |
383 | 392 | ||
384 | static loadWithAccountAndChannel (id: number | string, transaction: Transaction) { | 393 | static loadWithAccountAndChannel (id: number | string, transaction: Transaction): Bluebird<MVideoPlaylistFull> { |
385 | const where = buildWhereIdOrUUID(id) | 394 | const where = buildWhereIdOrUUID(id) |
386 | 395 | ||
387 | const query = { | 396 | const query = { |
@@ -394,7 +403,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> { | |||
394 | .findOne(query) | 403 | .findOne(query) |
395 | } | 404 | } |
396 | 405 | ||
397 | static loadByUrlAndPopulateAccount (url: string) { | 406 | static loadByUrlAndPopulateAccount (url: string): Bluebird<MVideoPlaylistAccountThumbnail> { |
398 | const query = { | 407 | const query = { |
399 | where: { | 408 | where: { |
400 | url | 409 | url |
@@ -423,7 +432,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> { | |||
423 | return VideoPlaylistModel.update({ privacy: VideoPlaylistPrivacy.PRIVATE, videoChannelId: null }, query) | 432 | return VideoPlaylistModel.update({ privacy: VideoPlaylistPrivacy.PRIVATE, videoChannelId: null }, query) |
424 | } | 433 | } |
425 | 434 | ||
426 | async setAndSaveThumbnail (thumbnail: ThumbnailModel, t: Transaction) { | 435 | async setAndSaveThumbnail (thumbnail: MThumbnail, t: Transaction) { |
427 | thumbnail.videoPlaylistId = this.id | 436 | thumbnail.videoPlaylistId = this.id |
428 | 437 | ||
429 | this.Thumbnail = await thumbnail.save({ transaction: t }) | 438 | this.Thumbnail = await thumbnail.save({ transaction: t }) |
@@ -471,7 +480,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> { | |||
471 | return isOutdated(this, ACTIVITY_PUB.VIDEO_PLAYLIST_REFRESH_INTERVAL) | 480 | return isOutdated(this, ACTIVITY_PUB.VIDEO_PLAYLIST_REFRESH_INTERVAL) |
472 | } | 481 | } |
473 | 482 | ||
474 | toFormattedJSON (): VideoPlaylist { | 483 | toFormattedJSON (this: MVideoPlaylistFormattable): VideoPlaylist { |
475 | return { | 484 | return { |
476 | id: this.id, | 485 | id: this.id, |
477 | uuid: this.uuid, | 486 | uuid: this.uuid, |
@@ -501,7 +510,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> { | |||
501 | } | 510 | } |
502 | } | 511 | } |
503 | 512 | ||
504 | toActivityPubObject (page: number, t: Transaction): Promise<PlaylistObject> { | 513 | toActivityPubObject (this: MVideoPlaylistAP, page: number, t: Transaction): Promise<PlaylistObject> { |
505 | const handler = (start: number, count: number) => { | 514 | const handler = (start: number, count: number) => { |
506 | return VideoPlaylistElementModel.listUrlsOfForAP(this.id, start, count, t) | 515 | return VideoPlaylistElementModel.listUrlsOfForAP(this.id, start, count, t) |
507 | } | 516 | } |
diff --git a/server/models/video/video-share.ts b/server/models/video/video-share.ts index d8ed64557..9019b401a 100644 --- a/server/models/video/video-share.ts +++ b/server/models/video/video-share.ts | |||
@@ -8,6 +8,8 @@ import { buildLocalActorIdsIn, throwIfNotValid } from '../utils' | |||
8 | import { VideoModel } from './video' | 8 | import { VideoModel } from './video' |
9 | import { VideoChannelModel } from './video-channel' | 9 | import { VideoChannelModel } from './video-channel' |
10 | import { Op, Transaction } from 'sequelize' | 10 | import { Op, Transaction } from 'sequelize' |
11 | import { MVideoShareActor, MVideoShareFull } from '../../typings/models/video' | ||
12 | import { MActorDefault } from '../../typings/models' | ||
11 | 13 | ||
12 | enum ScopeNames { | 14 | enum ScopeNames { |
13 | FULL = 'FULL', | 15 | FULL = 'FULL', |
@@ -88,7 +90,7 @@ export class VideoShareModel extends Model<VideoShareModel> { | |||
88 | }) | 90 | }) |
89 | Video: VideoModel | 91 | Video: VideoModel |
90 | 92 | ||
91 | static load (actorId: number, videoId: number, t?: Transaction) { | 93 | static load (actorId: number, videoId: number, t?: Transaction): Bluebird<MVideoShareActor> { |
92 | return VideoShareModel.scope(ScopeNames.WITH_ACTOR).findOne({ | 94 | return VideoShareModel.scope(ScopeNames.WITH_ACTOR).findOne({ |
93 | where: { | 95 | where: { |
94 | actorId, | 96 | actorId, |
@@ -98,7 +100,7 @@ export class VideoShareModel extends Model<VideoShareModel> { | |||
98 | }) | 100 | }) |
99 | } | 101 | } |
100 | 102 | ||
101 | static loadByUrl (url: string, t: Transaction) { | 103 | static loadByUrl (url: string, t: Transaction): Bluebird<MVideoShareFull> { |
102 | return VideoShareModel.scope(ScopeNames.FULL).findOne({ | 104 | return VideoShareModel.scope(ScopeNames.FULL).findOne({ |
103 | where: { | 105 | where: { |
104 | url | 106 | url |
@@ -107,7 +109,7 @@ export class VideoShareModel extends Model<VideoShareModel> { | |||
107 | }) | 109 | }) |
108 | } | 110 | } |
109 | 111 | ||
110 | static loadActorsByShare (videoId: number, t: Transaction) { | 112 | static loadActorsByShare (videoId: number, t: Transaction): Bluebird<MActorDefault[]> { |
111 | const query = { | 113 | const query = { |
112 | where: { | 114 | where: { |
113 | videoId | 115 | videoId |
@@ -122,10 +124,10 @@ export class VideoShareModel extends Model<VideoShareModel> { | |||
122 | } | 124 | } |
123 | 125 | ||
124 | return VideoShareModel.scope(ScopeNames.FULL).findAll(query) | 126 | return VideoShareModel.scope(ScopeNames.FULL).findAll(query) |
125 | .then(res => res.map(r => r.Actor)) | 127 | .then((res: MVideoShareFull[]) => res.map(r => r.Actor)) |
126 | } | 128 | } |
127 | 129 | ||
128 | static loadActorsWhoSharedVideosOf (actorOwnerId: number, t: Transaction): Bluebird<ActorModel[]> { | 130 | static loadActorsWhoSharedVideosOf (actorOwnerId: number, t: Transaction): Bluebird<MActorDefault[]> { |
129 | const query = { | 131 | const query = { |
130 | attributes: [], | 132 | attributes: [], |
131 | include: [ | 133 | include: [ |
@@ -163,7 +165,7 @@ export class VideoShareModel extends Model<VideoShareModel> { | |||
163 | .then(res => res.map(r => r.Actor)) | 165 | .then(res => res.map(r => r.Actor)) |
164 | } | 166 | } |
165 | 167 | ||
166 | static loadActorsByVideoChannel (videoChannelId: number, t: Transaction): Bluebird<ActorModel[]> { | 168 | static loadActorsByVideoChannel (videoChannelId: number, t: Transaction): Bluebird<MActorDefault[]> { |
167 | const query = { | 169 | const query = { |
168 | attributes: [], | 170 | attributes: [], |
169 | include: [ | 171 | include: [ |
diff --git a/server/models/video/video-streaming-playlist.ts b/server/models/video/video-streaming-playlist.ts index 31dc82c54..0ea90d28c 100644 --- a/server/models/video/video-streaming-playlist.ts +++ b/server/models/video/video-streaming-playlist.ts | |||
@@ -1,16 +1,16 @@ | |||
1 | import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, HasMany, Is, Model, Table, UpdatedAt, DataType } from 'sequelize-typescript' | 1 | import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, HasMany, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' |
2 | import { isVideoFileInfoHashValid } from '../../helpers/custom-validators/videos' | 2 | import { isVideoFileInfoHashValid } from '../../helpers/custom-validators/videos' |
3 | import { throwIfNotValid } from '../utils' | 3 | import { throwIfNotValid } from '../utils' |
4 | import { VideoModel } from './video' | 4 | import { VideoModel } from './video' |
5 | import { VideoRedundancyModel } from '../redundancy/video-redundancy' | 5 | import { VideoRedundancyModel } from '../redundancy/video-redundancy' |
6 | import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type' | 6 | import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type' |
7 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' | 7 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' |
8 | import { CONSTRAINTS_FIELDS, STATIC_PATHS, P2P_MEDIA_LOADER_PEER_VERSION } from '../../initializers/constants' | 8 | import { CONSTRAINTS_FIELDS, P2P_MEDIA_LOADER_PEER_VERSION, STATIC_PATHS } from '../../initializers/constants' |
9 | import { VideoFileModel } from './video-file' | ||
10 | import { join } from 'path' | 9 | import { join } from 'path' |
11 | import { sha1 } from '../../helpers/core-utils' | 10 | import { sha1 } from '../../helpers/core-utils' |
12 | import { isArrayOf } from '../../helpers/custom-validators/misc' | 11 | import { isArrayOf } from '../../helpers/custom-validators/misc' |
13 | import { QueryTypes, Op } from 'sequelize' | 12 | import { Op, QueryTypes } from 'sequelize' |
13 | import { MStreamingPlaylist, MVideoFile } from '@server/typings/models' | ||
14 | 14 | ||
15 | @Table({ | 15 | @Table({ |
16 | tableName: 'videoStreamingPlaylist', | 16 | tableName: 'videoStreamingPlaylist', |
@@ -91,7 +91,7 @@ export class VideoStreamingPlaylistModel extends Model<VideoStreamingPlaylistMod | |||
91 | .then(results => results.length === 1) | 91 | .then(results => results.length === 1) |
92 | } | 92 | } |
93 | 93 | ||
94 | static buildP2PMediaLoaderInfoHashes (playlistUrl: string, videoFiles: VideoFileModel[]) { | 94 | static buildP2PMediaLoaderInfoHashes (playlistUrl: string, videoFiles: MVideoFile[]) { |
95 | const hashes: string[] = [] | 95 | const hashes: string[] = [] |
96 | 96 | ||
97 | // https://github.com/Novage/p2p-media-loader/blob/master/p2p-media-loader-core/lib/p2p-media-manager.ts#L115 | 97 | // https://github.com/Novage/p2p-media-loader/blob/master/p2p-media-loader-core/lib/p2p-media-manager.ts#L115 |
@@ -165,7 +165,7 @@ export class VideoStreamingPlaylistModel extends Model<VideoStreamingPlaylistMod | |||
165 | return baseUrlHttp + STATIC_PATHS.REDUNDANCY + this.getStringType() + '/' + this.Video.uuid | 165 | return baseUrlHttp + STATIC_PATHS.REDUNDANCY + this.getStringType() + '/' + this.Video.uuid |
166 | } | 166 | } |
167 | 167 | ||
168 | hasSameUniqueKeysThan (other: VideoStreamingPlaylistModel) { | 168 | hasSameUniqueKeysThan (other: MStreamingPlaylist) { |
169 | return this.type === other.type && | 169 | return this.type === other.type && |
170 | this.videoId === other.videoId | 170 | this.videoId === other.videoId |
171 | } | 171 | } |
diff --git a/server/models/video/video.ts b/server/models/video/video.ts index b59df397d..6856dcd9f 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts | |||
@@ -36,7 +36,7 @@ import { | |||
36 | Table, | 36 | Table, |
37 | UpdatedAt | 37 | UpdatedAt |
38 | } from 'sequelize-typescript' | 38 | } from 'sequelize-typescript' |
39 | import { UserRight, VideoPrivacy, VideoResolution, VideoState } from '../../../shared' | 39 | import { UserRight, VideoPrivacy, VideoState } from '../../../shared' |
40 | import { VideoTorrentObject } from '../../../shared/models/activitypub/objects' | 40 | import { VideoTorrentObject } from '../../../shared/models/activitypub/objects' |
41 | import { Video, VideoDetails, VideoFile } from '../../../shared/models/videos' | 41 | import { Video, VideoDetails, VideoFile } from '../../../shared/models/videos' |
42 | import { VideoFilter } from '../../../shared/models/videos/video-query.type' | 42 | import { VideoFilter } from '../../../shared/models/videos/video-query.type' |
@@ -111,7 +111,6 @@ import { | |||
111 | videoModelToFormattedJSON | 111 | videoModelToFormattedJSON |
112 | } from './video-format-utils' | 112 | } from './video-format-utils' |
113 | import { UserVideoHistoryModel } from '../account/user-video-history' | 113 | import { UserVideoHistoryModel } from '../account/user-video-history' |
114 | import { UserModel } from '../account/user' | ||
115 | import { VideoImportModel } from './video-import' | 114 | import { VideoImportModel } from './video-import' |
116 | import { VideoStreamingPlaylistModel } from './video-streaming-playlist' | 115 | import { VideoStreamingPlaylistModel } from './video-streaming-playlist' |
117 | import { VideoPlaylistElementModel } from './video-playlist-element' | 116 | import { VideoPlaylistElementModel } from './video-playlist-element' |
@@ -120,6 +119,29 @@ import { ThumbnailModel } from './thumbnail' | |||
120 | import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type' | 119 | import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type' |
121 | import { createTorrentPromise } from '../../helpers/webtorrent' | 120 | import { createTorrentPromise } from '../../helpers/webtorrent' |
122 | import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type' | 121 | import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type' |
122 | import { | ||
123 | MChannel, | ||
124 | MChannelAccountDefault, | ||
125 | MChannelId, | ||
126 | MUserAccountId, | ||
127 | MUserId, | ||
128 | MVideoAccountLight, | ||
129 | MVideoAccountLightBlacklistAllFiles, | ||
130 | MVideoAP, | ||
131 | MVideoDetails, | ||
132 | MVideoFormattable, | ||
133 | MVideoFormattableDetails, | ||
134 | MVideoForUser, | ||
135 | MVideoFullLight, | ||
136 | MVideoIdThumbnail, | ||
137 | MVideoThumbnail, | ||
138 | MVideoThumbnailBlacklist, | ||
139 | MVideoWithAllFiles, | ||
140 | MVideoWithFile, | ||
141 | MVideoWithRights | ||
142 | } from '../../typings/models' | ||
143 | import { MVideoFile, MVideoFileRedundanciesOpt } from '../../typings/models/video/video-file' | ||
144 | import { MThumbnail } from '../../typings/models/video/thumbnail' | ||
123 | 145 | ||
124 | // FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation | 146 | // FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation |
125 | const indexes: (ModelIndexesOptions & { where?: WhereOptions })[] = [ | 147 | const indexes: (ModelIndexesOptions & { where?: WhereOptions })[] = [ |
@@ -232,8 +254,8 @@ export type AvailableForListIDsOptions = { | |||
232 | videoPlaylistId?: number | 254 | videoPlaylistId?: number |
233 | 255 | ||
234 | trendingDays?: number | 256 | trendingDays?: number |
235 | user?: UserModel, | 257 | user?: MUserAccountId |
236 | historyOfUser?: UserModel | 258 | historyOfUser?: MUserId |
237 | 259 | ||
238 | baseWhere?: WhereOptions[] | 260 | baseWhere?: WhereOptions[] |
239 | } | 261 | } |
@@ -446,13 +468,15 @@ export type AvailableForListIDsOptions = { | |||
446 | // FIXME: issues with sequelize count when making a join on n:m relation, so we just make a IN() | 468 | // FIXME: issues with sequelize count when making a join on n:m relation, so we just make a IN() |
447 | if (options.tagsAllOf || options.tagsOneOf) { | 469 | if (options.tagsAllOf || options.tagsOneOf) { |
448 | if (options.tagsOneOf) { | 470 | if (options.tagsOneOf) { |
471 | const tagsOneOfLower = options.tagsOneOf.map(t => t.toLowerCase()) | ||
472 | |||
449 | whereAnd.push({ | 473 | whereAnd.push({ |
450 | id: { | 474 | id: { |
451 | [ Op.in ]: Sequelize.literal( | 475 | [ Op.in ]: Sequelize.literal( |
452 | '(' + | 476 | '(' + |
453 | 'SELECT "videoId" FROM "videoTag" ' + | 477 | 'SELECT "videoId" FROM "videoTag" ' + |
454 | 'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' + | 478 | 'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' + |
455 | 'WHERE "tag"."name" IN (' + createSafeIn(VideoModel, options.tagsOneOf) + ')' + | 479 | 'WHERE lower("tag"."name") IN (' + createSafeIn(VideoModel, tagsOneOfLower) + ')' + |
456 | ')' | 480 | ')' |
457 | ) | 481 | ) |
458 | } | 482 | } |
@@ -460,14 +484,16 @@ export type AvailableForListIDsOptions = { | |||
460 | } | 484 | } |
461 | 485 | ||
462 | if (options.tagsAllOf) { | 486 | if (options.tagsAllOf) { |
487 | const tagsAllOfLower = options.tagsAllOf.map(t => t.toLowerCase()) | ||
488 | |||
463 | whereAnd.push({ | 489 | whereAnd.push({ |
464 | id: { | 490 | id: { |
465 | [ Op.in ]: Sequelize.literal( | 491 | [ Op.in ]: Sequelize.literal( |
466 | '(' + | 492 | '(' + |
467 | 'SELECT "videoId" FROM "videoTag" ' + | 493 | 'SELECT "videoId" FROM "videoTag" ' + |
468 | 'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' + | 494 | 'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' + |
469 | 'WHERE "tag"."name" IN (' + createSafeIn(VideoModel, options.tagsAllOf) + ')' + | 495 | 'WHERE lower("tag"."name") IN (' + createSafeIn(VideoModel, tagsAllOfLower) + ')' + |
470 | 'GROUP BY "videoTag"."videoId" HAVING COUNT(*) = ' + options.tagsAllOf.length + | 496 | 'GROUP BY "videoTag"."videoId" HAVING COUNT(*) = ' + tagsAllOfLower.length + |
471 | ')' | 497 | ')' |
472 | ) | 498 | ) |
473 | } | 499 | } |
@@ -634,7 +660,7 @@ export type AvailableForListIDsOptions = { | |||
634 | [ ScopeNames.WITH_BLACKLISTED ]: { | 660 | [ ScopeNames.WITH_BLACKLISTED ]: { |
635 | include: [ | 661 | include: [ |
636 | { | 662 | { |
637 | attributes: [ 'id', 'reason' ], | 663 | attributes: [ 'id', 'reason', 'unfederated' ], |
638 | model: VideoBlacklistModel, | 664 | model: VideoBlacklistModel, |
639 | required: false | 665 | required: false |
640 | } | 666 | } |
@@ -989,18 +1015,16 @@ export class VideoModel extends Model<VideoModel> { | |||
989 | VideoCaptions: VideoCaptionModel[] | 1015 | VideoCaptions: VideoCaptionModel[] |
990 | 1016 | ||
991 | @BeforeDestroy | 1017 | @BeforeDestroy |
992 | static async sendDelete (instance: VideoModel, options) { | 1018 | static async sendDelete (instance: MVideoAccountLight, options) { |
993 | if (instance.isOwned()) { | 1019 | if (instance.isOwned()) { |
994 | if (!instance.VideoChannel) { | 1020 | if (!instance.VideoChannel) { |
995 | instance.VideoChannel = await instance.$get('VideoChannel', { | 1021 | instance.VideoChannel = await instance.$get('VideoChannel', { |
996 | include: [ | 1022 | include: [ |
997 | { | 1023 | ActorModel, |
998 | model: AccountModel, | 1024 | AccountModel |
999 | include: [ ActorModel ] | ||
1000 | } | ||
1001 | ], | 1025 | ], |
1002 | transaction: options.transaction | 1026 | transaction: options.transaction |
1003 | }) as VideoChannelModel | 1027 | }) as MChannelAccountDefault |
1004 | } | 1028 | } |
1005 | 1029 | ||
1006 | return sendDeleteVideo(instance, options.transaction) | 1030 | return sendDeleteVideo(instance, options.transaction) |
@@ -1039,7 +1063,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1039 | return undefined | 1063 | return undefined |
1040 | } | 1064 | } |
1041 | 1065 | ||
1042 | static listLocal () { | 1066 | static listLocal (): Bluebird<MVideoWithAllFiles[]> { |
1043 | const query = { | 1067 | const query = { |
1044 | where: { | 1068 | where: { |
1045 | remote: false | 1069 | remote: false |
@@ -1159,7 +1183,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1159 | }) | 1183 | }) |
1160 | } | 1184 | } |
1161 | 1185 | ||
1162 | static listUserVideosForApi (accountId: number, start: number, count: number, sort: string, withFiles = false) { | 1186 | static listUserVideosForApi (accountId: number, start: number, count: number, sort: string) { |
1163 | function buildBaseQuery (): FindOptions { | 1187 | function buildBaseQuery (): FindOptions { |
1164 | return { | 1188 | return { |
1165 | offset: start, | 1189 | offset: start, |
@@ -1192,16 +1216,9 @@ export class VideoModel extends Model<VideoModel> { | |||
1192 | ScopeNames.WITH_THUMBNAILS | 1216 | ScopeNames.WITH_THUMBNAILS |
1193 | ] | 1217 | ] |
1194 | 1218 | ||
1195 | if (withFiles === true) { | ||
1196 | findQuery.include.push({ | ||
1197 | model: VideoFileModel.unscoped(), | ||
1198 | required: true | ||
1199 | }) | ||
1200 | } | ||
1201 | |||
1202 | return Promise.all([ | 1219 | return Promise.all([ |
1203 | VideoModel.count(countQuery), | 1220 | VideoModel.count(countQuery), |
1204 | VideoModel.scope(findScopes).findAll(findQuery) | 1221 | VideoModel.scope(findScopes).findAll<MVideoForUser>(findQuery) |
1205 | ]).then(([ count, rows ]) => { | 1222 | ]).then(([ count, rows ]) => { |
1206 | return { | 1223 | return { |
1207 | data: rows, | 1224 | data: rows, |
@@ -1228,8 +1245,8 @@ export class VideoModel extends Model<VideoModel> { | |||
1228 | followerActorId?: number | 1245 | followerActorId?: number |
1229 | videoPlaylistId?: number, | 1246 | videoPlaylistId?: number, |
1230 | trendingDays?: number, | 1247 | trendingDays?: number, |
1231 | user?: UserModel, | 1248 | user?: MUserAccountId, |
1232 | historyOfUser?: UserModel | 1249 | historyOfUser?: MUserId |
1233 | }, countVideos = true) { | 1250 | }, countVideos = true) { |
1234 | if (options.filter && options.filter === 'all-local' && !options.user.hasRight(UserRight.SEE_ALL_VIDEOS)) { | 1251 | if (options.filter && options.filter === 'all-local' && !options.user.hasRight(UserRight.SEE_ALL_VIDEOS)) { |
1235 | throw new Error('Try to filter all-local but no user has not the see all videos right') | 1252 | throw new Error('Try to filter all-local but no user has not the see all videos right') |
@@ -1294,7 +1311,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1294 | tagsAllOf?: string[] | 1311 | tagsAllOf?: string[] |
1295 | durationMin?: number // seconds | 1312 | durationMin?: number // seconds |
1296 | durationMax?: number // seconds | 1313 | durationMax?: number // seconds |
1297 | user?: UserModel, | 1314 | user?: MUserAccountId, |
1298 | filter?: VideoFilter | 1315 | filter?: VideoFilter |
1299 | }) { | 1316 | }) { |
1300 | const whereAnd = [] | 1317 | const whereAnd = [] |
@@ -1387,7 +1404,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1387 | return VideoModel.getAvailableForApi(query, queryOptions) | 1404 | return VideoModel.getAvailableForApi(query, queryOptions) |
1388 | } | 1405 | } |
1389 | 1406 | ||
1390 | static load (id: number | string, t?: Transaction) { | 1407 | static load (id: number | string, t?: Transaction): Bluebird<MVideoThumbnail> { |
1391 | const where = buildWhereIdOrUUID(id) | 1408 | const where = buildWhereIdOrUUID(id) |
1392 | const options = { | 1409 | const options = { |
1393 | where, | 1410 | where, |
@@ -1397,7 +1414,20 @@ export class VideoModel extends Model<VideoModel> { | |||
1397 | return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(options) | 1414 | return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(options) |
1398 | } | 1415 | } |
1399 | 1416 | ||
1400 | static loadWithRights (id: number | string, t?: Transaction) { | 1417 | static loadWithBlacklist (id: number | string, t?: Transaction): Bluebird<MVideoThumbnailBlacklist> { |
1418 | const where = buildWhereIdOrUUID(id) | ||
1419 | const options = { | ||
1420 | where, | ||
1421 | transaction: t | ||
1422 | } | ||
1423 | |||
1424 | return VideoModel.scope([ | ||
1425 | ScopeNames.WITH_THUMBNAILS, | ||
1426 | ScopeNames.WITH_BLACKLISTED | ||
1427 | ]).findOne(options) | ||
1428 | } | ||
1429 | |||
1430 | static loadWithRights (id: number | string, t?: Transaction): Bluebird<MVideoWithRights> { | ||
1401 | const where = buildWhereIdOrUUID(id) | 1431 | const where = buildWhereIdOrUUID(id) |
1402 | const options = { | 1432 | const options = { |
1403 | where, | 1433 | where, |
@@ -1411,7 +1441,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1411 | ]).findOne(options) | 1441 | ]).findOne(options) |
1412 | } | 1442 | } |
1413 | 1443 | ||
1414 | static loadOnlyId (id: number | string, t?: Transaction) { | 1444 | static loadOnlyId (id: number | string, t?: Transaction): Bluebird<MVideoIdThumbnail> { |
1415 | const where = buildWhereIdOrUUID(id) | 1445 | const where = buildWhereIdOrUUID(id) |
1416 | 1446 | ||
1417 | const options = { | 1447 | const options = { |
@@ -1423,7 +1453,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1423 | return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(options) | 1453 | return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(options) |
1424 | } | 1454 | } |
1425 | 1455 | ||
1426 | static loadWithFiles (id: number | string, t?: Transaction, logging?: boolean) { | 1456 | static loadWithFiles (id: number | string, t?: Transaction, logging?: boolean): Bluebird<MVideoWithAllFiles> { |
1427 | const where = buildWhereIdOrUUID(id) | 1457 | const where = buildWhereIdOrUUID(id) |
1428 | 1458 | ||
1429 | const query = { | 1459 | const query = { |
@@ -1439,7 +1469,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1439 | ]).findOne(query) | 1469 | ]).findOne(query) |
1440 | } | 1470 | } |
1441 | 1471 | ||
1442 | static loadByUUID (uuid: string) { | 1472 | static loadByUUID (uuid: string): Bluebird<MVideoThumbnail> { |
1443 | const options = { | 1473 | const options = { |
1444 | where: { | 1474 | where: { |
1445 | uuid | 1475 | uuid |
@@ -1449,7 +1479,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1449 | return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(options) | 1479 | return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(options) |
1450 | } | 1480 | } |
1451 | 1481 | ||
1452 | static loadByUrl (url: string, transaction?: Transaction) { | 1482 | static loadByUrl (url: string, transaction?: Transaction): Bluebird<MVideoThumbnail> { |
1453 | const query: FindOptions = { | 1483 | const query: FindOptions = { |
1454 | where: { | 1484 | where: { |
1455 | url | 1485 | url |
@@ -1460,7 +1490,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1460 | return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(query) | 1490 | return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(query) |
1461 | } | 1491 | } |
1462 | 1492 | ||
1463 | static loadByUrlAndPopulateAccount (url: string, transaction?: Transaction) { | 1493 | static loadByUrlAndPopulateAccount (url: string, transaction?: Transaction): Bluebird<MVideoAccountLightBlacklistAllFiles> { |
1464 | const query: FindOptions = { | 1494 | const query: FindOptions = { |
1465 | where: { | 1495 | where: { |
1466 | url | 1496 | url |
@@ -1472,11 +1502,12 @@ export class VideoModel extends Model<VideoModel> { | |||
1472 | ScopeNames.WITH_ACCOUNT_DETAILS, | 1502 | ScopeNames.WITH_ACCOUNT_DETAILS, |
1473 | ScopeNames.WITH_FILES, | 1503 | ScopeNames.WITH_FILES, |
1474 | ScopeNames.WITH_STREAMING_PLAYLISTS, | 1504 | ScopeNames.WITH_STREAMING_PLAYLISTS, |
1475 | ScopeNames.WITH_THUMBNAILS | 1505 | ScopeNames.WITH_THUMBNAILS, |
1506 | ScopeNames.WITH_BLACKLISTED | ||
1476 | ]).findOne(query) | 1507 | ]).findOne(query) |
1477 | } | 1508 | } |
1478 | 1509 | ||
1479 | static loadAndPopulateAccountAndServerAndTags (id: number | string, t?: Transaction, userId?: number) { | 1510 | static loadAndPopulateAccountAndServerAndTags (id: number | string, t?: Transaction, userId?: number): Bluebird<MVideoFullLight> { |
1480 | const where = buildWhereIdOrUUID(id) | 1511 | const where = buildWhereIdOrUUID(id) |
1481 | 1512 | ||
1482 | const options = { | 1513 | const options = { |
@@ -1508,7 +1539,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1508 | id: number | string, | 1539 | id: number | string, |
1509 | t?: Transaction, | 1540 | t?: Transaction, |
1510 | userId?: number | 1541 | userId?: number |
1511 | }) { | 1542 | }): Bluebird<MVideoDetails> { |
1512 | const { id, t, userId } = parameters | 1543 | const { id, t, userId } = parameters |
1513 | const where = buildWhereIdOrUUID(id) | 1544 | const where = buildWhereIdOrUUID(id) |
1514 | 1545 | ||
@@ -1586,7 +1617,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1586 | .then(results => results.length === 1) | 1617 | .then(results => results.length === 1) |
1587 | } | 1618 | } |
1588 | 1619 | ||
1589 | static bulkUpdateSupportField (videoChannel: VideoChannelModel, t: Transaction) { | 1620 | static bulkUpdateSupportField (videoChannel: MChannel, t: Transaction) { |
1590 | const options = { | 1621 | const options = { |
1591 | where: { | 1622 | where: { |
1592 | channelId: videoChannel.id | 1623 | channelId: videoChannel.id |
@@ -1597,7 +1628,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1597 | return VideoModel.update({ support: videoChannel.support }, options) | 1628 | return VideoModel.update({ support: videoChannel.support }, options) |
1598 | } | 1629 | } |
1599 | 1630 | ||
1600 | static getAllIdsFromChannel (videoChannel: VideoChannelModel) { | 1631 | static getAllIdsFromChannel (videoChannel: MChannelId): Bluebird<number[]> { |
1601 | const query = { | 1632 | const query = { |
1602 | attributes: [ 'id' ], | 1633 | attributes: [ 'id' ], |
1603 | where: { | 1634 | where: { |
@@ -1756,20 +1787,20 @@ export class VideoModel extends Model<VideoModel> { | |||
1756 | this.VideoChannel.Account.isBlocked() | 1787 | this.VideoChannel.Account.isBlocked() |
1757 | } | 1788 | } |
1758 | 1789 | ||
1759 | getOriginalFile () { | 1790 | getOriginalFile <T extends MVideoWithFile> (this: T) { |
1760 | if (Array.isArray(this.VideoFiles) === false) return undefined | 1791 | if (Array.isArray(this.VideoFiles) === false) return undefined |
1761 | 1792 | ||
1762 | // The original file is the file that have the higher resolution | 1793 | // The original file is the file that have the higher resolution |
1763 | return maxBy(this.VideoFiles, file => file.resolution) | 1794 | return maxBy(this.VideoFiles, file => file.resolution) |
1764 | } | 1795 | } |
1765 | 1796 | ||
1766 | getFile (resolution: number) { | 1797 | getFile <T extends MVideoWithFile> (this: T, resolution: number) { |
1767 | if (Array.isArray(this.VideoFiles) === false) return undefined | 1798 | if (Array.isArray(this.VideoFiles) === false) return undefined |
1768 | 1799 | ||
1769 | return this.VideoFiles.find(f => f.resolution === resolution) | 1800 | return this.VideoFiles.find(f => f.resolution === resolution) |
1770 | } | 1801 | } |
1771 | 1802 | ||
1772 | async addAndSaveThumbnail (thumbnail: ThumbnailModel, transaction: Transaction) { | 1803 | async addAndSaveThumbnail (thumbnail: MThumbnail, transaction: Transaction) { |
1773 | thumbnail.videoId = this.id | 1804 | thumbnail.videoId = this.id |
1774 | 1805 | ||
1775 | const savedThumbnail = await thumbnail.save({ transaction }) | 1806 | const savedThumbnail = await thumbnail.save({ transaction }) |
@@ -1782,7 +1813,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1782 | this.Thumbnails.push(savedThumbnail) | 1813 | this.Thumbnails.push(savedThumbnail) |
1783 | } | 1814 | } |
1784 | 1815 | ||
1785 | getVideoFilename (videoFile: VideoFileModel) { | 1816 | getVideoFilename (videoFile: MVideoFile) { |
1786 | return this.uuid + '-' + videoFile.resolution + videoFile.extname | 1817 | return this.uuid + '-' + videoFile.resolution + videoFile.extname |
1787 | } | 1818 | } |
1788 | 1819 | ||
@@ -1806,7 +1837,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1806 | return this.Thumbnails.find(t => t.type === ThumbnailType.PREVIEW) | 1837 | return this.Thumbnails.find(t => t.type === ThumbnailType.PREVIEW) |
1807 | } | 1838 | } |
1808 | 1839 | ||
1809 | getTorrentFileName (videoFile: VideoFileModel) { | 1840 | getTorrentFileName (videoFile: MVideoFile) { |
1810 | const extension = '.torrent' | 1841 | const extension = '.torrent' |
1811 | return this.uuid + '-' + videoFile.resolution + extension | 1842 | return this.uuid + '-' + videoFile.resolution + extension |
1812 | } | 1843 | } |
@@ -1815,15 +1846,15 @@ export class VideoModel extends Model<VideoModel> { | |||
1815 | return this.remote === false | 1846 | return this.remote === false |
1816 | } | 1847 | } |
1817 | 1848 | ||
1818 | getTorrentFilePath (videoFile: VideoFileModel) { | 1849 | getTorrentFilePath (videoFile: MVideoFile) { |
1819 | return join(CONFIG.STORAGE.TORRENTS_DIR, this.getTorrentFileName(videoFile)) | 1850 | return join(CONFIG.STORAGE.TORRENTS_DIR, this.getTorrentFileName(videoFile)) |
1820 | } | 1851 | } |
1821 | 1852 | ||
1822 | getVideoFilePath (videoFile: VideoFileModel) { | 1853 | getVideoFilePath (videoFile: MVideoFile) { |
1823 | return join(CONFIG.STORAGE.VIDEOS_DIR, this.getVideoFilename(videoFile)) | 1854 | return join(CONFIG.STORAGE.VIDEOS_DIR, this.getVideoFilename(videoFile)) |
1824 | } | 1855 | } |
1825 | 1856 | ||
1826 | async createTorrentAndSetInfoHash (videoFile: VideoFileModel) { | 1857 | async createTorrentAndSetInfoHash (videoFile: MVideoFile) { |
1827 | const options = { | 1858 | const options = { |
1828 | // Keep the extname, it's used by the client to stream the file inside a web browser | 1859 | // Keep the extname, it's used by the client to stream the file inside a web browser |
1829 | name: `${this.name} ${videoFile.resolution}p${videoFile.extname}`, | 1860 | name: `${this.name} ${videoFile.resolution}p${videoFile.extname}`, |
@@ -1869,11 +1900,11 @@ export class VideoModel extends Model<VideoModel> { | |||
1869 | return join(LAZY_STATIC_PATHS.PREVIEWS, preview.filename) | 1900 | return join(LAZY_STATIC_PATHS.PREVIEWS, preview.filename) |
1870 | } | 1901 | } |
1871 | 1902 | ||
1872 | toFormattedJSON (options?: VideoFormattingJSONOptions): Video { | 1903 | toFormattedJSON (this: MVideoFormattable, options?: VideoFormattingJSONOptions): Video { |
1873 | return videoModelToFormattedJSON(this, options) | 1904 | return videoModelToFormattedJSON(this, options) |
1874 | } | 1905 | } |
1875 | 1906 | ||
1876 | toFormattedDetailsJSON (): VideoDetails { | 1907 | toFormattedDetailsJSON (this: MVideoFormattableDetails): VideoDetails { |
1877 | return videoModelToFormattedDetailsJSON(this) | 1908 | return videoModelToFormattedDetailsJSON(this) |
1878 | } | 1909 | } |
1879 | 1910 | ||
@@ -1881,7 +1912,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1881 | return videoFilesModelToFormattedJSON(this, this.VideoFiles) | 1912 | return videoFilesModelToFormattedJSON(this, this.VideoFiles) |
1882 | } | 1913 | } |
1883 | 1914 | ||
1884 | toActivityPubObject (): VideoTorrentObject { | 1915 | toActivityPubObject (this: MVideoAP): VideoTorrentObject { |
1885 | return videoModelToActivityPubObject(this) | 1916 | return videoModelToActivityPubObject(this) |
1886 | } | 1917 | } |
1887 | 1918 | ||
@@ -1908,7 +1939,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1908 | return this.VideoStreamingPlaylists.find(p => p.type === VideoStreamingPlaylistType.HLS) | 1939 | return this.VideoStreamingPlaylists.find(p => p.type === VideoStreamingPlaylistType.HLS) |
1909 | } | 1940 | } |
1910 | 1941 | ||
1911 | removeFile (videoFile: VideoFileModel, isRedundancy = false) { | 1942 | removeFile (videoFile: MVideoFile, isRedundancy = false) { |
1912 | const baseDir = isRedundancy ? CONFIG.STORAGE.REDUNDANCY_DIR : CONFIG.STORAGE.VIDEOS_DIR | 1943 | const baseDir = isRedundancy ? CONFIG.STORAGE.REDUNDANCY_DIR : CONFIG.STORAGE.VIDEOS_DIR |
1913 | 1944 | ||
1914 | const filePath = join(baseDir, this.getVideoFilename(videoFile)) | 1945 | const filePath = join(baseDir, this.getVideoFilename(videoFile)) |
@@ -1916,7 +1947,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1916 | .catch(err => logger.warn('Cannot delete file %s.', filePath, { err })) | 1947 | .catch(err => logger.warn('Cannot delete file %s.', filePath, { err })) |
1917 | } | 1948 | } |
1918 | 1949 | ||
1919 | removeTorrent (videoFile: VideoFileModel) { | 1950 | removeTorrent (videoFile: MVideoFile) { |
1920 | const torrentPath = join(CONFIG.STORAGE.TORRENTS_DIR, this.getTorrentFileName(videoFile)) | 1951 | const torrentPath = join(CONFIG.STORAGE.TORRENTS_DIR, this.getTorrentFileName(videoFile)) |
1921 | return remove(torrentPath) | 1952 | return remove(torrentPath) |
1922 | .catch(err => logger.warn('Cannot delete torrent %s.', torrentPath, { err })) | 1953 | .catch(err => logger.warn('Cannot delete torrent %s.', torrentPath, { err })) |
@@ -1957,7 +1988,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1957 | return { baseUrlHttp, baseUrlWs } | 1988 | return { baseUrlHttp, baseUrlWs } |
1958 | } | 1989 | } |
1959 | 1990 | ||
1960 | generateMagnetUri (videoFile: VideoFileModel, baseUrlHttp: string, baseUrlWs: string) { | 1991 | generateMagnetUri (videoFile: MVideoFileRedundanciesOpt, baseUrlHttp: string, baseUrlWs: string) { |
1961 | const xs = this.getTorrentUrl(videoFile, baseUrlHttp) | 1992 | const xs = this.getTorrentUrl(videoFile, baseUrlHttp) |
1962 | const announce = this.getTrackerUrls(baseUrlHttp, baseUrlWs) | 1993 | const announce = this.getTrackerUrls(baseUrlHttp, baseUrlWs) |
1963 | let urlList = [ this.getVideoFileUrl(videoFile, baseUrlHttp) ] | 1994 | let urlList = [ this.getVideoFileUrl(videoFile, baseUrlHttp) ] |
@@ -1980,27 +2011,27 @@ export class VideoModel extends Model<VideoModel> { | |||
1980 | return [ baseUrlWs + '/tracker/socket', baseUrlHttp + '/tracker/announce' ] | 2011 | return [ baseUrlWs + '/tracker/socket', baseUrlHttp + '/tracker/announce' ] |
1981 | } | 2012 | } |
1982 | 2013 | ||
1983 | getTorrentUrl (videoFile: VideoFileModel, baseUrlHttp: string) { | 2014 | getTorrentUrl (videoFile: MVideoFile, baseUrlHttp: string) { |
1984 | return baseUrlHttp + STATIC_PATHS.TORRENTS + this.getTorrentFileName(videoFile) | 2015 | return baseUrlHttp + STATIC_PATHS.TORRENTS + this.getTorrentFileName(videoFile) |
1985 | } | 2016 | } |
1986 | 2017 | ||
1987 | getTorrentDownloadUrl (videoFile: VideoFileModel, baseUrlHttp: string) { | 2018 | getTorrentDownloadUrl (videoFile: MVideoFile, baseUrlHttp: string) { |
1988 | return baseUrlHttp + STATIC_DOWNLOAD_PATHS.TORRENTS + this.getTorrentFileName(videoFile) | 2019 | return baseUrlHttp + STATIC_DOWNLOAD_PATHS.TORRENTS + this.getTorrentFileName(videoFile) |
1989 | } | 2020 | } |
1990 | 2021 | ||
1991 | getVideoFileUrl (videoFile: VideoFileModel, baseUrlHttp: string) { | 2022 | getVideoFileUrl (videoFile: MVideoFile, baseUrlHttp: string) { |
1992 | return baseUrlHttp + STATIC_PATHS.WEBSEED + this.getVideoFilename(videoFile) | 2023 | return baseUrlHttp + STATIC_PATHS.WEBSEED + this.getVideoFilename(videoFile) |
1993 | } | 2024 | } |
1994 | 2025 | ||
1995 | getVideoRedundancyUrl (videoFile: VideoFileModel, baseUrlHttp: string) { | 2026 | getVideoRedundancyUrl (videoFile: MVideoFile, baseUrlHttp: string) { |
1996 | return baseUrlHttp + STATIC_PATHS.REDUNDANCY + this.getVideoFilename(videoFile) | 2027 | return baseUrlHttp + STATIC_PATHS.REDUNDANCY + this.getVideoFilename(videoFile) |
1997 | } | 2028 | } |
1998 | 2029 | ||
1999 | getVideoFileDownloadUrl (videoFile: VideoFileModel, baseUrlHttp: string) { | 2030 | getVideoFileDownloadUrl (videoFile: MVideoFile, baseUrlHttp: string) { |
2000 | return baseUrlHttp + STATIC_DOWNLOAD_PATHS.VIDEOS + this.getVideoFilename(videoFile) | 2031 | return baseUrlHttp + STATIC_DOWNLOAD_PATHS.VIDEOS + this.getVideoFilename(videoFile) |
2001 | } | 2032 | } |
2002 | 2033 | ||
2003 | getBandwidthBits (videoFile: VideoFileModel) { | 2034 | getBandwidthBits (videoFile: MVideoFile) { |
2004 | return Math.ceil((videoFile.size * 8) / this.duration) | 2035 | return Math.ceil((videoFile.size * 8) / this.duration) |
2005 | } | 2036 | } |
2006 | } | 2037 | } |
diff --git a/server/tests/api/activitypub/helpers.ts b/server/tests/api/activitypub/helpers.ts index 365d0e1ae..0d1f154fe 100644 --- a/server/tests/api/activitypub/helpers.ts +++ b/server/tests/api/activitypub/helpers.ts | |||
@@ -53,19 +53,6 @@ describe('Test activity pub helpers', function () { | |||
53 | expect(result).to.be.false | 53 | expect(result).to.be.false |
54 | }) | 54 | }) |
55 | 55 | ||
56 | it('Should fail with an invalid PeerTube URL', async function () { | ||
57 | const keys = require('./json/peertube/keys.json') | ||
58 | const body = require('./json/peertube/announce-without-context.json') | ||
59 | |||
60 | const actorSignature = { url: 'http://localhost:9002/accounts/peertube', privateKey: keys.privateKey } | ||
61 | const signedBody = await buildSignedActivity(actorSignature as any, body) | ||
62 | |||
63 | const fromActor = { publicKey: keys.publicKey, url: 'http://localhost:9003/accounts/peertube' } | ||
64 | const result = await isJsonLDSignatureVerified(fromActor as any, signedBody) | ||
65 | |||
66 | expect(result).to.be.false | ||
67 | }) | ||
68 | |||
69 | it('Should succeed with a valid PeerTube signature', async function () { | 56 | it('Should succeed with a valid PeerTube signature', async function () { |
70 | const keys = require('./json/peertube/keys.json') | 57 | const keys = require('./json/peertube/keys.json') |
71 | const body = require('./json/peertube/announce-without-context.json') | 58 | const body = require('./json/peertube/announce-without-context.json') |
diff --git a/server/tests/api/check-params/config.ts b/server/tests/api/check-params/config.ts index 7773ae1e7..9435bb1e8 100644 --- a/server/tests/api/check-params/config.ts +++ b/server/tests/api/check-params/config.ts | |||
@@ -5,8 +5,16 @@ import 'mocha' | |||
5 | import { CustomConfig } from '../../../../shared/models/server/custom-config.model' | 5 | import { CustomConfig } from '../../../../shared/models/server/custom-config.model' |
6 | 6 | ||
7 | import { | 7 | import { |
8 | createUser, flushTests, killallServers, makeDeleteRequest, makeGetRequest, makePutBodyRequest, flushAndRunServer, ServerInfo, | 8 | cleanupTests, |
9 | setAccessTokensToServers, userLogin, immutableAssign, cleanupTests | 9 | createUser, |
10 | flushAndRunServer, | ||
11 | immutableAssign, | ||
12 | makeDeleteRequest, | ||
13 | makeGetRequest, | ||
14 | makePutBodyRequest, | ||
15 | ServerInfo, | ||
16 | setAccessTokensToServers, | ||
17 | userLogin | ||
10 | } from '../../../../shared/extra-utils' | 18 | } from '../../../../shared/extra-utils' |
11 | 19 | ||
12 | describe('Test config API validators', function () { | 20 | describe('Test config API validators', function () { |
@@ -19,6 +27,18 @@ describe('Test config API validators', function () { | |||
19 | shortDescription: 'my short description', | 27 | shortDescription: 'my short description', |
20 | description: 'my super description', | 28 | description: 'my super description', |
21 | terms: 'my super terms', | 29 | terms: 'my super terms', |
30 | codeOfConduct: 'my super coc', | ||
31 | |||
32 | creationReason: 'my super reason', | ||
33 | moderationInformation: 'my super moderation information', | ||
34 | administrator: 'Kuja', | ||
35 | maintenanceLifetime: 'forever', | ||
36 | businessModel: 'my super business model', | ||
37 | hardwareInformation: '2vCore 3GB RAM', | ||
38 | |||
39 | languages: [ 'en', 'es' ], | ||
40 | categories: [ 1, 2 ], | ||
41 | |||
22 | isNSFW: true, | 42 | isNSFW: true, |
23 | defaultClientRoute: '/videos/recently-added', | 43 | defaultClientRoute: '/videos/recently-added', |
24 | defaultNSFWPolicy: 'blur', | 44 | defaultNSFWPolicy: 'blur', |
@@ -98,6 +118,17 @@ describe('Test config API validators', function () { | |||
98 | enabled: false, | 118 | enabled: false, |
99 | manualApproval: true | 119 | manualApproval: true |
100 | } | 120 | } |
121 | }, | ||
122 | followings: { | ||
123 | instance: { | ||
124 | autoFollowBack: { | ||
125 | enabled: true | ||
126 | }, | ||
127 | autoFollowIndex: { | ||
128 | enabled: true, | ||
129 | indexUrl: 'https://index.example.com' | ||
130 | } | ||
131 | } | ||
101 | } | 132 | } |
102 | } | 133 | } |
103 | 134 | ||
diff --git a/server/tests/api/check-params/user-notifications.ts b/server/tests/api/check-params/user-notifications.ts index 14ee20d45..3b06be7ef 100644 --- a/server/tests/api/check-params/user-notifications.ts +++ b/server/tests/api/check-params/user-notifications.ts | |||
@@ -172,7 +172,8 @@ describe('Test user notifications API validators', function () { | |||
172 | commentMention: UserNotificationSettingValue.WEB, | 172 | commentMention: UserNotificationSettingValue.WEB, |
173 | newFollow: UserNotificationSettingValue.WEB, | 173 | newFollow: UserNotificationSettingValue.WEB, |
174 | newUserRegistration: UserNotificationSettingValue.WEB, | 174 | newUserRegistration: UserNotificationSettingValue.WEB, |
175 | newInstanceFollower: UserNotificationSettingValue.WEB | 175 | newInstanceFollower: UserNotificationSettingValue.WEB, |
176 | autoInstanceFollowing: UserNotificationSettingValue.WEB | ||
176 | } | 177 | } |
177 | 178 | ||
178 | it('Should fail with missing fields', async function () { | 179 | it('Should fail with missing fields', async function () { |
diff --git a/server/tests/api/check-params/users.ts b/server/tests/api/check-params/users.ts index 939b919ed..55094795c 100644 --- a/server/tests/api/check-params/users.ts +++ b/server/tests/api/check-params/users.ts | |||
@@ -476,6 +476,22 @@ describe('Test users API validators', function () { | |||
476 | await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields }) | 476 | await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields }) |
477 | }) | 477 | }) |
478 | 478 | ||
479 | it('Should fail with an invalid noInstanceConfigWarningModal attribute', async function () { | ||
480 | const fields = { | ||
481 | noInstanceConfigWarningModal: -1 | ||
482 | } | ||
483 | |||
484 | await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields }) | ||
485 | }) | ||
486 | |||
487 | it('Should fail with an invalid noWelcomeModal attribute', async function () { | ||
488 | const fields = { | ||
489 | noWelcomeModal: -1 | ||
490 | } | ||
491 | |||
492 | await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields }) | ||
493 | }) | ||
494 | |||
479 | it('Should succeed to change password with the correct params', async function () { | 495 | it('Should succeed to change password with the correct params', async function () { |
480 | const fields = { | 496 | const fields = { |
481 | currentPassword: 'my super password', | 497 | currentPassword: 'my super password', |
@@ -483,7 +499,9 @@ describe('Test users API validators', function () { | |||
483 | nsfwPolicy: 'blur', | 499 | nsfwPolicy: 'blur', |
484 | autoPlayVideo: false, | 500 | autoPlayVideo: false, |
485 | email: 'super_email@example.com', | 501 | email: 'super_email@example.com', |
486 | theme: 'default' | 502 | theme: 'default', |
503 | noInstanceConfigWarningModal: true, | ||
504 | noWelcomeModal: true | ||
487 | } | 505 | } |
488 | 506 | ||
489 | await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields, statusCodeExpected: 204 }) | 507 | await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields, statusCodeExpected: 204 }) |
diff --git a/server/tests/api/notifications/user-notifications.ts b/server/tests/api/notifications/user-notifications.ts index 6fa630562..15a34f5aa 100644 --- a/server/tests/api/notifications/user-notifications.ts +++ b/server/tests/api/notifications/user-notifications.ts | |||
@@ -14,10 +14,13 @@ import { | |||
14 | getVideoCommentThreads, | 14 | getVideoCommentThreads, |
15 | getVideoThreadComments, | 15 | getVideoThreadComments, |
16 | immutableAssign, | 16 | immutableAssign, |
17 | MockInstancesIndex, | ||
17 | registerUser, | 18 | registerUser, |
18 | removeVideoFromBlacklist, | 19 | removeVideoFromBlacklist, |
19 | reportVideoAbuse, | 20 | reportVideoAbuse, |
21 | unfollow, | ||
20 | updateCustomConfig, | 22 | updateCustomConfig, |
23 | updateCustomSubConfig, | ||
21 | updateMyUser, | 24 | updateMyUser, |
22 | updateVideo, | 25 | updateVideo, |
23 | updateVideoChannel, | 26 | updateVideoChannel, |
@@ -29,6 +32,7 @@ import { setAccessTokensToServers } from '../../../../shared/extra-utils/users/l | |||
29 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' | 32 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' |
30 | import { getUserNotificationSocket } from '../../../../shared/extra-utils/socket/socket-io' | 33 | import { getUserNotificationSocket } from '../../../../shared/extra-utils/socket/socket-io' |
31 | import { | 34 | import { |
35 | checkAutoInstanceFollowing, | ||
32 | checkCommentMention, | 36 | checkCommentMention, |
33 | CheckerBaseParams, | 37 | CheckerBaseParams, |
34 | checkMyVideoImportIsFinished, | 38 | checkMyVideoImportIsFinished, |
@@ -108,7 +112,8 @@ describe('Test users notifications', function () { | |||
108 | commentMention: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, | 112 | commentMention: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, |
109 | newFollow: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, | 113 | newFollow: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, |
110 | newUserRegistration: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, | 114 | newUserRegistration: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, |
111 | newInstanceFollower: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL | 115 | newInstanceFollower: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, |
116 | autoInstanceFollowing: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL | ||
112 | } | 117 | } |
113 | 118 | ||
114 | before(async function () { | 119 | before(async function () { |
@@ -873,7 +878,18 @@ describe('Test users notifications', function () { | |||
873 | }) | 878 | }) |
874 | }) | 879 | }) |
875 | 880 | ||
876 | describe('New instance follower', function () { | 881 | describe('New instance follows', function () { |
882 | const instanceIndexServer = new MockInstancesIndex() | ||
883 | const config = { | ||
884 | followings: { | ||
885 | instance: { | ||
886 | autoFollowIndex: { | ||
887 | indexUrl: 'http://localhost:42100', | ||
888 | enabled: true | ||
889 | } | ||
890 | } | ||
891 | } | ||
892 | } | ||
877 | let baseParams: CheckerBaseParams | 893 | let baseParams: CheckerBaseParams |
878 | 894 | ||
879 | before(async () => { | 895 | before(async () => { |
@@ -883,6 +899,9 @@ describe('Test users notifications', function () { | |||
883 | socketNotifications: adminNotifications, | 899 | socketNotifications: adminNotifications, |
884 | token: servers[0].accessToken | 900 | token: servers[0].accessToken |
885 | } | 901 | } |
902 | |||
903 | await instanceIndexServer.initialize() | ||
904 | instanceIndexServer.addInstance(servers[1].host) | ||
886 | }) | 905 | }) |
887 | 906 | ||
888 | it('Should send a notification only to admin when there is a new instance follower', async function () { | 907 | it('Should send a notification only to admin when there is a new instance follower', async function () { |
@@ -897,6 +916,56 @@ describe('Test users notifications', function () { | |||
897 | const userOverride = { socketNotifications: userNotifications, token: userAccessToken, check: { web: true, mail: false } } | 916 | const userOverride = { socketNotifications: userNotifications, token: userAccessToken, check: { web: true, mail: false } } |
898 | await checkNewInstanceFollower(immutableAssign(baseParams, userOverride), 'localhost:' + servers[2].port, 'absence') | 917 | await checkNewInstanceFollower(immutableAssign(baseParams, userOverride), 'localhost:' + servers[2].port, 'absence') |
899 | }) | 918 | }) |
919 | |||
920 | it('Should send a notification on auto follow back', async function () { | ||
921 | this.timeout(40000) | ||
922 | |||
923 | await unfollow(servers[2].url, servers[2].accessToken, servers[0]) | ||
924 | await waitJobs(servers) | ||
925 | |||
926 | const config = { | ||
927 | followings: { | ||
928 | instance: { | ||
929 | autoFollowBack: { enabled: true } | ||
930 | } | ||
931 | } | ||
932 | } | ||
933 | await updateCustomSubConfig(servers[0].url, servers[0].accessToken, config) | ||
934 | |||
935 | await follow(servers[2].url, [ servers[0].url ], servers[2].accessToken) | ||
936 | |||
937 | await waitJobs(servers) | ||
938 | |||
939 | const followerHost = servers[0].host | ||
940 | const followingHost = servers[2].host | ||
941 | await checkAutoInstanceFollowing(baseParams, followerHost, followingHost, 'presence') | ||
942 | |||
943 | const userOverride = { socketNotifications: userNotifications, token: userAccessToken, check: { web: true, mail: false } } | ||
944 | await checkAutoInstanceFollowing(immutableAssign(baseParams, userOverride), followerHost, followingHost, 'absence') | ||
945 | |||
946 | config.followings.instance.autoFollowBack.enabled = false | ||
947 | await updateCustomSubConfig(servers[0].url, servers[0].accessToken, config) | ||
948 | await unfollow(servers[0].url, servers[0].accessToken, servers[2]) | ||
949 | await unfollow(servers[2].url, servers[2].accessToken, servers[0]) | ||
950 | }) | ||
951 | |||
952 | it('Should send a notification on auto instances index follow', async function () { | ||
953 | this.timeout(30000) | ||
954 | await unfollow(servers[0].url, servers[0].accessToken, servers[1]) | ||
955 | |||
956 | await updateCustomSubConfig(servers[0].url, servers[0].accessToken, config) | ||
957 | |||
958 | await wait(5000) | ||
959 | await waitJobs(servers) | ||
960 | |||
961 | const followerHost = servers[0].host | ||
962 | const followingHost = servers[1].host | ||
963 | await checkAutoInstanceFollowing(baseParams, followerHost, followingHost, 'presence') | ||
964 | |||
965 | config.followings.instance.autoFollowIndex.enabled = false | ||
966 | await updateCustomSubConfig(servers[0].url, servers[0].accessToken, config) | ||
967 | await unfollow(servers[0].url, servers[0].accessToken, servers[1]) | ||
968 | }) | ||
900 | }) | 969 | }) |
901 | 970 | ||
902 | describe('New actor follow', function () { | 971 | describe('New actor follow', function () { |
diff --git a/server/tests/api/search/search-videos.ts b/server/tests/api/search/search-videos.ts index c06200ffe..a3e05156b 100644 --- a/server/tests/api/search/search-videos.ts +++ b/server/tests/api/search/search-videos.ts | |||
@@ -206,7 +206,7 @@ describe('Test videos search', function () { | |||
206 | const query = { | 206 | const query = { |
207 | search: '9999', | 207 | search: '9999', |
208 | categoryOneOf: [ 1 ], | 208 | categoryOneOf: [ 1 ], |
209 | tagsOneOf: [ 'aaaa', 'ffff' ] | 209 | tagsOneOf: [ 'aAaa', 'ffff' ] |
210 | } | 210 | } |
211 | const res1 = await advancedVideosSearch(server.url, query) | 211 | const res1 = await advancedVideosSearch(server.url, query) |
212 | expect(res1.body.total).to.equal(2) | 212 | expect(res1.body.total).to.equal(2) |
@@ -219,15 +219,15 @@ describe('Test videos search', function () { | |||
219 | const query = { | 219 | const query = { |
220 | search: '9999', | 220 | search: '9999', |
221 | categoryOneOf: [ 1 ], | 221 | categoryOneOf: [ 1 ], |
222 | tagsAllOf: [ 'cccc' ] | 222 | tagsAllOf: [ 'CCcc' ] |
223 | } | 223 | } |
224 | const res1 = await advancedVideosSearch(server.url, query) | 224 | const res1 = await advancedVideosSearch(server.url, query) |
225 | expect(res1.body.total).to.equal(2) | 225 | expect(res1.body.total).to.equal(2) |
226 | 226 | ||
227 | const res2 = await advancedVideosSearch(server.url, immutableAssign(query, { tagsAllOf: [ 'blabla' ] })) | 227 | const res2 = await advancedVideosSearch(server.url, immutableAssign(query, { tagsAllOf: [ 'blAbla' ] })) |
228 | expect(res2.body.total).to.equal(0) | 228 | expect(res2.body.total).to.equal(0) |
229 | 229 | ||
230 | const res3 = await advancedVideosSearch(server.url, immutableAssign(query, { tagsAllOf: [ 'bbbb', 'cccc' ] })) | 230 | const res3 = await advancedVideosSearch(server.url, immutableAssign(query, { tagsAllOf: [ 'bbbb', 'CCCC' ] })) |
231 | expect(res3.body.total).to.equal(1) | 231 | expect(res3.body.total).to.equal(1) |
232 | }) | 232 | }) |
233 | 233 | ||
diff --git a/server/tests/api/server/auto-follows.ts b/server/tests/api/server/auto-follows.ts new file mode 100644 index 000000000..df468034c --- /dev/null +++ b/server/tests/api/server/auto-follows.ts | |||
@@ -0,0 +1,211 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | ||
2 | |||
3 | import * as chai from 'chai' | ||
4 | import 'mocha' | ||
5 | import { | ||
6 | acceptFollower, | ||
7 | cleanupTests, | ||
8 | flushAndRunMultipleServers, | ||
9 | MockInstancesIndex, | ||
10 | ServerInfo, | ||
11 | setAccessTokensToServers, | ||
12 | unfollow, | ||
13 | updateCustomSubConfig, | ||
14 | wait | ||
15 | } from '../../../../shared/extra-utils/index' | ||
16 | import { follow, getFollowersListPaginationAndSort, getFollowingListPaginationAndSort } from '../../../../shared/extra-utils/server/follows' | ||
17 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' | ||
18 | import { ActorFollow } from '../../../../shared/models/actors' | ||
19 | |||
20 | const expect = chai.expect | ||
21 | |||
22 | async function checkFollow (follower: ServerInfo, following: ServerInfo, exists: boolean) { | ||
23 | { | ||
24 | const res = await getFollowersListPaginationAndSort(following.url, 0, 5, '-createdAt') | ||
25 | const follows = res.body.data as ActorFollow[] | ||
26 | |||
27 | const follow = follows.find(f => { | ||
28 | return f.follower.host === follower.host && f.state === 'accepted' | ||
29 | }) | ||
30 | |||
31 | if (exists === true) { | ||
32 | expect(follow).to.exist | ||
33 | } else { | ||
34 | expect(follow).to.be.undefined | ||
35 | } | ||
36 | } | ||
37 | |||
38 | { | ||
39 | const res = await getFollowingListPaginationAndSort(follower.url, 0, 5, '-createdAt') | ||
40 | const follows = res.body.data as ActorFollow[] | ||
41 | |||
42 | const follow = follows.find(f => { | ||
43 | return f.following.host === following.host && f.state === 'accepted' | ||
44 | }) | ||
45 | |||
46 | if (exists === true) { | ||
47 | expect(follow).to.exist | ||
48 | } else { | ||
49 | expect(follow).to.be.undefined | ||
50 | } | ||
51 | } | ||
52 | } | ||
53 | |||
54 | async function server1Follows2 (servers: ServerInfo[]) { | ||
55 | await follow(servers[0].url, [ servers[1].host ], servers[0].accessToken) | ||
56 | |||
57 | await waitJobs(servers) | ||
58 | } | ||
59 | |||
60 | async function resetFollows (servers: ServerInfo[]) { | ||
61 | try { | ||
62 | await unfollow(servers[ 0 ].url, servers[ 0 ].accessToken, servers[ 1 ]) | ||
63 | await unfollow(servers[ 1 ].url, servers[ 1 ].accessToken, servers[ 0 ]) | ||
64 | } catch { /* empty */ } | ||
65 | |||
66 | await waitJobs(servers) | ||
67 | |||
68 | await checkFollow(servers[0], servers[1], false) | ||
69 | await checkFollow(servers[1], servers[0], false) | ||
70 | } | ||
71 | |||
72 | describe('Test auto follows', function () { | ||
73 | let servers: ServerInfo[] = [] | ||
74 | |||
75 | before(async function () { | ||
76 | this.timeout(30000) | ||
77 | |||
78 | servers = await flushAndRunMultipleServers(3) | ||
79 | |||
80 | // Get the access tokens | ||
81 | await setAccessTokensToServers(servers) | ||
82 | }) | ||
83 | |||
84 | describe('Auto follow back', function () { | ||
85 | |||
86 | it('Should not auto follow back if the option is not enabled', async function () { | ||
87 | this.timeout(15000) | ||
88 | |||
89 | await server1Follows2(servers) | ||
90 | |||
91 | await checkFollow(servers[0], servers[1], true) | ||
92 | await checkFollow(servers[1], servers[0], false) | ||
93 | |||
94 | await resetFollows(servers) | ||
95 | }) | ||
96 | |||
97 | it('Should auto follow back on auto accept if the option is enabled', async function () { | ||
98 | this.timeout(15000) | ||
99 | |||
100 | const config = { | ||
101 | followings: { | ||
102 | instance: { | ||
103 | autoFollowBack: { enabled: true } | ||
104 | } | ||
105 | } | ||
106 | } | ||
107 | await updateCustomSubConfig(servers[1].url, servers[1].accessToken, config) | ||
108 | |||
109 | await server1Follows2(servers) | ||
110 | |||
111 | await checkFollow(servers[0], servers[1], true) | ||
112 | await checkFollow(servers[1], servers[0], true) | ||
113 | |||
114 | await resetFollows(servers) | ||
115 | }) | ||
116 | |||
117 | it('Should wait the acceptation before auto follow back', async function () { | ||
118 | this.timeout(30000) | ||
119 | |||
120 | const config = { | ||
121 | followings: { | ||
122 | instance: { | ||
123 | autoFollowBack: { enabled: true } | ||
124 | } | ||
125 | }, | ||
126 | followers: { | ||
127 | instance: { | ||
128 | manualApproval: true | ||
129 | } | ||
130 | } | ||
131 | } | ||
132 | await updateCustomSubConfig(servers[1].url, servers[1].accessToken, config) | ||
133 | |||
134 | await server1Follows2(servers) | ||
135 | |||
136 | await checkFollow(servers[0], servers[1], false) | ||
137 | await checkFollow(servers[1], servers[0], false) | ||
138 | |||
139 | await acceptFollower(servers[1].url, servers[1].accessToken, 'peertube@' + servers[0].host) | ||
140 | await waitJobs(servers) | ||
141 | |||
142 | await checkFollow(servers[0], servers[1], true) | ||
143 | await checkFollow(servers[1], servers[0], true) | ||
144 | |||
145 | await resetFollows(servers) | ||
146 | |||
147 | config.followings.instance.autoFollowBack.enabled = false | ||
148 | config.followers.instance.manualApproval = false | ||
149 | await updateCustomSubConfig(servers[1].url, servers[1].accessToken, config) | ||
150 | }) | ||
151 | }) | ||
152 | |||
153 | describe('Auto follow index', function () { | ||
154 | const instanceIndexServer = new MockInstancesIndex() | ||
155 | |||
156 | before(async () => { | ||
157 | await instanceIndexServer.initialize() | ||
158 | }) | ||
159 | |||
160 | it('Should not auto follow index if the option is not enabled', async function () { | ||
161 | this.timeout(30000) | ||
162 | |||
163 | await wait(5000) | ||
164 | await waitJobs(servers) | ||
165 | |||
166 | await checkFollow(servers[ 0 ], servers[ 1 ], false) | ||
167 | await checkFollow(servers[ 1 ], servers[ 0 ], false) | ||
168 | }) | ||
169 | |||
170 | it('Should auto follow the index', async function () { | ||
171 | this.timeout(30000) | ||
172 | |||
173 | instanceIndexServer.addInstance(servers[1].host) | ||
174 | |||
175 | const config = { | ||
176 | followings: { | ||
177 | instance: { | ||
178 | autoFollowIndex: { | ||
179 | indexUrl: 'http://localhost:42100', | ||
180 | enabled: true | ||
181 | } | ||
182 | } | ||
183 | } | ||
184 | } | ||
185 | await updateCustomSubConfig(servers[0].url, servers[0].accessToken, config) | ||
186 | |||
187 | await wait(5000) | ||
188 | await waitJobs(servers) | ||
189 | |||
190 | await checkFollow(servers[ 0 ], servers[ 1 ], true) | ||
191 | |||
192 | await resetFollows(servers) | ||
193 | }) | ||
194 | |||
195 | it('Should follow new added instances in the index but not old ones', async function () { | ||
196 | this.timeout(30000) | ||
197 | |||
198 | instanceIndexServer.addInstance(servers[2].host) | ||
199 | |||
200 | await wait(5000) | ||
201 | await waitJobs(servers) | ||
202 | |||
203 | await checkFollow(servers[ 0 ], servers[ 1 ], false) | ||
204 | await checkFollow(servers[ 0 ], servers[ 2 ], true) | ||
205 | }) | ||
206 | }) | ||
207 | |||
208 | after(async function () { | ||
209 | await cleanupTests(servers) | ||
210 | }) | ||
211 | }) | ||
diff --git a/server/tests/api/server/config.ts b/server/tests/api/server/config.ts index 78fdc9cc0..97cc99eea 100644 --- a/server/tests/api/server/config.ts +++ b/server/tests/api/server/config.ts | |||
@@ -28,7 +28,19 @@ function checkInitialConfig (server: ServerInfo, data: CustomConfig) { | |||
28 | 'with WebTorrent and Angular.' | 28 | 'with WebTorrent and Angular.' |
29 | ) | 29 | ) |
30 | expect(data.instance.description).to.equal('Welcome to this PeerTube instance!') | 30 | expect(data.instance.description).to.equal('Welcome to this PeerTube instance!') |
31 | |||
31 | expect(data.instance.terms).to.equal('No terms for now.') | 32 | expect(data.instance.terms).to.equal('No terms for now.') |
33 | expect(data.instance.creationReason).to.be.empty | ||
34 | expect(data.instance.codeOfConduct).to.be.empty | ||
35 | expect(data.instance.moderationInformation).to.be.empty | ||
36 | expect(data.instance.administrator).to.be.empty | ||
37 | expect(data.instance.maintenanceLifetime).to.be.empty | ||
38 | expect(data.instance.businessModel).to.be.empty | ||
39 | expect(data.instance.hardwareInformation).to.be.empty | ||
40 | |||
41 | expect(data.instance.languages).to.have.lengthOf(0) | ||
42 | expect(data.instance.categories).to.have.lengthOf(0) | ||
43 | |||
32 | expect(data.instance.defaultClientRoute).to.equal('/videos/trending') | 44 | expect(data.instance.defaultClientRoute).to.equal('/videos/trending') |
33 | expect(data.instance.isNSFW).to.be.false | 45 | expect(data.instance.isNSFW).to.be.false |
34 | expect(data.instance.defaultNSFWPolicy).to.equal('display') | 46 | expect(data.instance.defaultNSFWPolicy).to.equal('display') |
@@ -68,13 +80,29 @@ function checkInitialConfig (server: ServerInfo, data: CustomConfig) { | |||
68 | 80 | ||
69 | expect(data.followers.instance.enabled).to.be.true | 81 | expect(data.followers.instance.enabled).to.be.true |
70 | expect(data.followers.instance.manualApproval).to.be.false | 82 | expect(data.followers.instance.manualApproval).to.be.false |
83 | |||
84 | expect(data.followings.instance.autoFollowBack.enabled).to.be.false | ||
85 | expect(data.followings.instance.autoFollowIndex.enabled).to.be.false | ||
86 | expect(data.followings.instance.autoFollowIndex.indexUrl).to.equal('https://instances.joinpeertube.org') | ||
71 | } | 87 | } |
72 | 88 | ||
73 | function checkUpdatedConfig (data: CustomConfig) { | 89 | function checkUpdatedConfig (data: CustomConfig) { |
74 | expect(data.instance.name).to.equal('PeerTube updated') | 90 | expect(data.instance.name).to.equal('PeerTube updated') |
75 | expect(data.instance.shortDescription).to.equal('my short description') | 91 | expect(data.instance.shortDescription).to.equal('my short description') |
76 | expect(data.instance.description).to.equal('my super description') | 92 | expect(data.instance.description).to.equal('my super description') |
93 | |||
77 | expect(data.instance.terms).to.equal('my super terms') | 94 | expect(data.instance.terms).to.equal('my super terms') |
95 | expect(data.instance.creationReason).to.equal('my super creation reason') | ||
96 | expect(data.instance.codeOfConduct).to.equal('my super coc') | ||
97 | expect(data.instance.moderationInformation).to.equal('my super moderation information') | ||
98 | expect(data.instance.administrator).to.equal('Kuja') | ||
99 | expect(data.instance.maintenanceLifetime).to.equal('forever') | ||
100 | expect(data.instance.businessModel).to.equal('my super business model') | ||
101 | expect(data.instance.hardwareInformation).to.equal('2vCore 3GB RAM') | ||
102 | |||
103 | expect(data.instance.languages).to.deep.equal([ 'en', 'es' ]) | ||
104 | expect(data.instance.categories).to.deep.equal([ 1, 2 ]) | ||
105 | |||
78 | expect(data.instance.defaultClientRoute).to.equal('/videos/recently-added') | 106 | expect(data.instance.defaultClientRoute).to.equal('/videos/recently-added') |
79 | expect(data.instance.isNSFW).to.be.true | 107 | expect(data.instance.isNSFW).to.be.true |
80 | expect(data.instance.defaultNSFWPolicy).to.equal('blur') | 108 | expect(data.instance.defaultNSFWPolicy).to.equal('blur') |
@@ -119,6 +147,10 @@ function checkUpdatedConfig (data: CustomConfig) { | |||
119 | 147 | ||
120 | expect(data.followers.instance.enabled).to.be.false | 148 | expect(data.followers.instance.enabled).to.be.false |
121 | expect(data.followers.instance.manualApproval).to.be.true | 149 | expect(data.followers.instance.manualApproval).to.be.true |
150 | |||
151 | expect(data.followings.instance.autoFollowBack.enabled).to.be.true | ||
152 | expect(data.followings.instance.autoFollowIndex.enabled).to.be.true | ||
153 | expect(data.followings.instance.autoFollowIndex.indexUrl).to.equal('https://updated.example.com') | ||
122 | } | 154 | } |
123 | 155 | ||
124 | describe('Test config', function () { | 156 | describe('Test config', function () { |
@@ -182,6 +214,18 @@ describe('Test config', function () { | |||
182 | shortDescription: 'my short description', | 214 | shortDescription: 'my short description', |
183 | description: 'my super description', | 215 | description: 'my super description', |
184 | terms: 'my super terms', | 216 | terms: 'my super terms', |
217 | codeOfConduct: 'my super coc', | ||
218 | |||
219 | creationReason: 'my super creation reason', | ||
220 | moderationInformation: 'my super moderation information', | ||
221 | administrator: 'Kuja', | ||
222 | maintenanceLifetime: 'forever', | ||
223 | businessModel: 'my super business model', | ||
224 | hardwareInformation: '2vCore 3GB RAM', | ||
225 | |||
226 | languages: [ 'en', 'es' ], | ||
227 | categories: [ 1, 2 ], | ||
228 | |||
185 | defaultClientRoute: '/videos/recently-added', | 229 | defaultClientRoute: '/videos/recently-added', |
186 | isNSFW: true, | 230 | isNSFW: true, |
187 | defaultNSFWPolicy: 'blur' as 'blur', | 231 | defaultNSFWPolicy: 'blur' as 'blur', |
@@ -261,6 +305,17 @@ describe('Test config', function () { | |||
261 | enabled: false, | 305 | enabled: false, |
262 | manualApproval: true | 306 | manualApproval: true |
263 | } | 307 | } |
308 | }, | ||
309 | followings: { | ||
310 | instance: { | ||
311 | autoFollowBack: { | ||
312 | enabled: true | ||
313 | }, | ||
314 | autoFollowIndex: { | ||
315 | enabled: true, | ||
316 | indexUrl: 'https://updated.example.com' | ||
317 | } | ||
318 | } | ||
264 | } | 319 | } |
265 | } | 320 | } |
266 | await updateCustomConfig(server.url, server.accessToken, newCustomConfig) | 321 | await updateCustomConfig(server.url, server.accessToken, newCustomConfig) |
@@ -310,6 +365,17 @@ describe('Test config', function () { | |||
310 | expect(data.instance.shortDescription).to.equal('my short description') | 365 | expect(data.instance.shortDescription).to.equal('my short description') |
311 | expect(data.instance.description).to.equal('my super description') | 366 | expect(data.instance.description).to.equal('my super description') |
312 | expect(data.instance.terms).to.equal('my super terms') | 367 | expect(data.instance.terms).to.equal('my super terms') |
368 | expect(data.instance.codeOfConduct).to.equal('my super coc') | ||
369 | |||
370 | expect(data.instance.creationReason).to.equal('my super creation reason') | ||
371 | expect(data.instance.moderationInformation).to.equal('my super moderation information') | ||
372 | expect(data.instance.administrator).to.equal('Kuja') | ||
373 | expect(data.instance.maintenanceLifetime).to.equal('forever') | ||
374 | expect(data.instance.businessModel).to.equal('my super business model') | ||
375 | expect(data.instance.hardwareInformation).to.equal('2vCore 3GB RAM') | ||
376 | |||
377 | expect(data.instance.languages).to.deep.equal([ 'en', 'es' ]) | ||
378 | expect(data.instance.categories).to.deep.equal([ 1, 2 ]) | ||
313 | }) | 379 | }) |
314 | 380 | ||
315 | it('Should remove the custom configuration', async function () { | 381 | it('Should remove the custom configuration', async function () { |
diff --git a/server/tests/api/server/index.ts b/server/tests/api/server/index.ts index 3daeeb49a..08205b2c8 100644 --- a/server/tests/api/server/index.ts +++ b/server/tests/api/server/index.ts | |||
@@ -1,3 +1,4 @@ | |||
1 | import './auto-follows' | ||
1 | import './config' | 2 | import './config' |
2 | import './contact-form' | 3 | import './contact-form' |
3 | import './email' | 4 | import './email' |
diff --git a/server/tests/api/users/users.ts b/server/tests/api/users/users.ts index 3a3fabb4c..95b1bb626 100644 --- a/server/tests/api/users/users.ts +++ b/server/tests/api/users/users.ts | |||
@@ -442,7 +442,7 @@ describe('Test users', function () { | |||
442 | url: server.url, | 442 | url: server.url, |
443 | accessToken: accessTokenUser, | 443 | accessToken: accessTokenUser, |
444 | currentPassword: 'super password', | 444 | currentPassword: 'super password', |
445 | newPassword: 'new password' | 445 | password: 'new password' |
446 | }) | 446 | }) |
447 | user.password = 'new password' | 447 | user.password = 'new password' |
448 | 448 | ||
@@ -543,7 +543,7 @@ describe('Test users', function () { | |||
543 | }) | 543 | }) |
544 | 544 | ||
545 | const res = await getMyUserInformation(server.url, accessTokenUser) | 545 | const res = await getMyUserInformation(server.url, accessTokenUser) |
546 | const user = res.body | 546 | const user: User = res.body |
547 | 547 | ||
548 | expect(user.username).to.equal('user_1') | 548 | expect(user.username).to.equal('user_1') |
549 | expect(user.email).to.equal('updated@example.com') | 549 | expect(user.email).to.equal('updated@example.com') |
@@ -552,6 +552,8 @@ describe('Test users', function () { | |||
552 | expect(user.id).to.be.a('number') | 552 | expect(user.id).to.be.a('number') |
553 | expect(user.account.displayName).to.equal('new display name') | 553 | expect(user.account.displayName).to.equal('new display name') |
554 | expect(user.account.description).to.equal('my super description updated') | 554 | expect(user.account.description).to.equal('my super description updated') |
555 | expect(user.noWelcomeModal).to.be.false | ||
556 | expect(user.noInstanceConfigWarningModal).to.be.false | ||
555 | }) | 557 | }) |
556 | 558 | ||
557 | it('Should be able to update my theme', async function () { | 559 | it('Should be able to update my theme', async function () { |
@@ -568,6 +570,21 @@ describe('Test users', function () { | |||
568 | expect(body.theme).to.equal(theme) | 570 | expect(body.theme).to.equal(theme) |
569 | } | 571 | } |
570 | }) | 572 | }) |
573 | |||
574 | it('Should be able to update my modal preferences', async function () { | ||
575 | await updateMyUser({ | ||
576 | url: server.url, | ||
577 | accessToken: accessTokenUser, | ||
578 | noInstanceConfigWarningModal: true, | ||
579 | noWelcomeModal: true | ||
580 | }) | ||
581 | |||
582 | const res = await getMyUserInformation(server.url, accessTokenUser) | ||
583 | const user: User = res.body | ||
584 | |||
585 | expect(user.noWelcomeModal).to.be.true | ||
586 | expect(user.noInstanceConfigWarningModal).to.be.true | ||
587 | }) | ||
571 | }) | 588 | }) |
572 | 589 | ||
573 | describe('Updating another user', function () { | 590 | describe('Updating another user', function () { |
diff --git a/server/tests/api/videos/video-abuse.ts b/server/tests/api/videos/video-abuse.ts index a2f3ee161..0cd6f22c7 100644 --- a/server/tests/api/videos/video-abuse.ts +++ b/server/tests/api/videos/video-abuse.ts | |||
@@ -17,6 +17,12 @@ import { | |||
17 | } from '../../../../shared/extra-utils/index' | 17 | } from '../../../../shared/extra-utils/index' |
18 | import { doubleFollow } from '../../../../shared/extra-utils/server/follows' | 18 | import { doubleFollow } from '../../../../shared/extra-utils/server/follows' |
19 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' | 19 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' |
20 | import { | ||
21 | addAccountToServerBlocklist, | ||
22 | addServerToServerBlocklist, | ||
23 | removeAccountFromServerBlocklist, | ||
24 | removeServerFromServerBlocklist | ||
25 | } from '../../../../shared/extra-utils/users/blocklist' | ||
20 | 26 | ||
21 | const expect = chai.expect | 27 | const expect = chai.expect |
22 | 28 | ||
@@ -163,13 +169,76 @@ describe('Test video abuses', function () { | |||
163 | expect(res.body.data[0].moderationComment).to.equal('It is valid') | 169 | expect(res.body.data[0].moderationComment).to.equal('It is valid') |
164 | }) | 170 | }) |
165 | 171 | ||
172 | it('Should hide video abuses from blocked accounts', async function () { | ||
173 | this.timeout(10000) | ||
174 | |||
175 | { | ||
176 | await reportVideoAbuse(servers[1].url, servers[1].accessToken, servers[0].video.uuid, 'will mute this') | ||
177 | await waitJobs(servers) | ||
178 | |||
179 | const res = await getVideoAbusesList(servers[0].url, servers[0].accessToken) | ||
180 | expect(res.body.total).to.equal(3) | ||
181 | } | ||
182 | |||
183 | const accountToBlock = 'root@localhost:' + servers[1].port | ||
184 | |||
185 | { | ||
186 | await addAccountToServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, accountToBlock) | ||
187 | |||
188 | const res = await getVideoAbusesList(servers[ 0 ].url, servers[ 0 ].accessToken) | ||
189 | expect(res.body.total).to.equal(2) | ||
190 | |||
191 | const abuse = res.body.data.find(a => a.reason === 'will mute this') | ||
192 | expect(abuse).to.be.undefined | ||
193 | } | ||
194 | |||
195 | { | ||
196 | await removeAccountFromServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, accountToBlock) | ||
197 | |||
198 | const res = await getVideoAbusesList(servers[ 0 ].url, servers[ 0 ].accessToken) | ||
199 | expect(res.body.total).to.equal(3) | ||
200 | } | ||
201 | }) | ||
202 | |||
203 | it('Should hide video abuses from blocked servers', async function () { | ||
204 | const serverToBlock = servers[1].host | ||
205 | |||
206 | { | ||
207 | await addServerToServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, servers[1].host) | ||
208 | |||
209 | const res = await getVideoAbusesList(servers[ 0 ].url, servers[ 0 ].accessToken) | ||
210 | expect(res.body.total).to.equal(2) | ||
211 | |||
212 | const abuse = res.body.data.find(a => a.reason === 'will mute this') | ||
213 | expect(abuse).to.be.undefined | ||
214 | } | ||
215 | |||
216 | { | ||
217 | await removeServerFromServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, serverToBlock) | ||
218 | |||
219 | const res = await getVideoAbusesList(servers[ 0 ].url, servers[ 0 ].accessToken) | ||
220 | expect(res.body.total).to.equal(3) | ||
221 | } | ||
222 | }) | ||
223 | |||
166 | it('Should delete the video abuse', async function () { | 224 | it('Should delete the video abuse', async function () { |
225 | this.timeout(10000) | ||
226 | |||
167 | await deleteVideoAbuse(servers[1].url, servers[1].accessToken, abuseServer2.video.uuid, abuseServer2.id) | 227 | await deleteVideoAbuse(servers[1].url, servers[1].accessToken, abuseServer2.video.uuid, abuseServer2.id) |
168 | 228 | ||
169 | const res = await getVideoAbusesList(servers[1].url, servers[1].accessToken) | 229 | await waitJobs(servers) |
170 | expect(res.body.total).to.equal(0) | 230 | |
171 | expect(res.body.data).to.be.an('array') | 231 | { |
172 | expect(res.body.data.length).to.equal(0) | 232 | const res = await getVideoAbusesList(servers[1].url, servers[1].accessToken) |
233 | expect(res.body.total).to.equal(1) | ||
234 | expect(res.body.data.length).to.equal(1) | ||
235 | expect(res.body.data[0].id).to.not.equal(abuseServer2.id) | ||
236 | } | ||
237 | |||
238 | { | ||
239 | const res = await getVideoAbusesList(servers[0].url, servers[0].accessToken) | ||
240 | expect(res.body.total).to.equal(3) | ||
241 | } | ||
173 | }) | 242 | }) |
174 | 243 | ||
175 | after(async function () { | 244 | after(async function () { |
diff --git a/server/tests/api/videos/video-change-ownership.ts b/server/tests/api/videos/video-change-ownership.ts index 3a3add71b..64ee2355a 100644 --- a/server/tests/api/videos/video-change-ownership.ts +++ b/server/tests/api/videos/video-change-ownership.ts | |||
@@ -191,7 +191,7 @@ describe('Test video change ownership - nominal', function () { | |||
191 | await waitJobs(servers) | 191 | await waitJobs(servers) |
192 | }) | 192 | }) |
193 | 193 | ||
194 | it('Should have video channel updated', async function () { | 194 | it('Should have the channel of the video updated', async function () { |
195 | for (const server of servers) { | 195 | for (const server of servers) { |
196 | const res = await getVideo(server.url, servers[0].video.uuid) | 196 | const res = await getVideo(server.url, servers[0].video.uuid) |
197 | 197 | ||
diff --git a/server/tools/cli.ts b/server/tools/cli.ts index 8599a270f..58e2445ac 100644 --- a/server/tools/cli.ts +++ b/server/tools/cli.ts | |||
@@ -5,6 +5,7 @@ import { root } from '../../shared/extra-utils/miscs/miscs' | |||
5 | import { getVideoChannel } from '../../shared/extra-utils/videos/video-channels' | 5 | import { getVideoChannel } from '../../shared/extra-utils/videos/video-channels' |
6 | import { Command } from 'commander' | 6 | import { Command } from 'commander' |
7 | import { VideoChannel, VideoPrivacy } from '../../shared/models/videos' | 7 | import { VideoChannel, VideoPrivacy } from '../../shared/models/videos' |
8 | import { createLogger, format, transports } from 'winston' | ||
8 | 9 | ||
9 | let configName = 'PeerTube/CLI' | 10 | let configName = 'PeerTube/CLI' |
10 | if (isTestInstance()) configName += `-${getAppNumber()}` | 11 | if (isTestInstance()) configName += `-${getAppNumber()}` |
@@ -119,6 +120,7 @@ function buildCommonVideoOptions (command: Command) { | |||
119 | .option('-m, --comments-enabled', 'Enable comments') | 120 | .option('-m, --comments-enabled', 'Enable comments') |
120 | .option('-s, --support <support>', 'Video support text') | 121 | .option('-s, --support <support>', 'Video support text') |
121 | .option('-w, --wait-transcoding', 'Wait transcoding before publishing the video') | 122 | .option('-w, --wait-transcoding', 'Wait transcoding before publishing the video') |
123 | .option('-v, --verbose <verbose>', 'Verbosity, from 0/\'error\' to 4/\'debug\'', 'info') | ||
122 | } | 124 | } |
123 | 125 | ||
124 | async function buildVideoAttributesFromCommander (url: string, command: Command, defaultAttributes: any = {}) { | 126 | async function buildVideoAttributesFromCommander (url: string, command: Command, defaultAttributes: any = {}) { |
@@ -175,11 +177,42 @@ function getServerCredentials (program: any) { | |||
175 | }) | 177 | }) |
176 | } | 178 | } |
177 | 179 | ||
180 | function getLogger (logLevel = 'info') { | ||
181 | const logLevels = { | ||
182 | 0: 0, | ||
183 | error: 0, | ||
184 | 1: 1, | ||
185 | warn: 1, | ||
186 | 2: 2, | ||
187 | info: 2, | ||
188 | 3: 3, | ||
189 | verbose: 3, | ||
190 | 4: 4, | ||
191 | debug: 4 | ||
192 | } | ||
193 | |||
194 | const logger = createLogger({ | ||
195 | levels: logLevels, | ||
196 | format: format.combine( | ||
197 | format.splat(), | ||
198 | format.simple() | ||
199 | ), | ||
200 | transports: [ | ||
201 | new (transports.Console)({ | ||
202 | level: logLevel | ||
203 | }) | ||
204 | ] | ||
205 | }) | ||
206 | |||
207 | return logger | ||
208 | } | ||
209 | |||
178 | // --------------------------------------------------------------------------- | 210 | // --------------------------------------------------------------------------- |
179 | 211 | ||
180 | export { | 212 | export { |
181 | version, | 213 | version, |
182 | config, | 214 | config, |
215 | getLogger, | ||
183 | getSettings, | 216 | getSettings, |
184 | getNetrc, | 217 | getNetrc, |
185 | getRemoteObjectOrDie, | 218 | getRemoteObjectOrDie, |
diff --git a/server/tools/peertube-import-videos.ts b/server/tools/peertube-import-videos.ts index 0ebfa7442..fcb90cca3 100644 --- a/server/tools/peertube-import-videos.ts +++ b/server/tools/peertube-import-videos.ts | |||
@@ -8,10 +8,11 @@ import { CONSTRAINTS_FIELDS } from '../initializers/constants' | |||
8 | import { getClient, getVideoCategories, login, searchVideoWithSort, uploadVideo } from '../../shared/extra-utils/index' | 8 | import { getClient, getVideoCategories, login, searchVideoWithSort, uploadVideo } from '../../shared/extra-utils/index' |
9 | import { truncate } from 'lodash' | 9 | import { truncate } from 'lodash' |
10 | import * as prompt from 'prompt' | 10 | import * as prompt from 'prompt' |
11 | import { accessSync, constants } from 'fs' | ||
11 | import { remove } from 'fs-extra' | 12 | import { remove } from 'fs-extra' |
12 | import { sha256 } from '../helpers/core-utils' | 13 | import { sha256 } from '../helpers/core-utils' |
13 | import { buildOriginallyPublishedAt, safeGetYoutubeDL } from '../helpers/youtube-dl' | 14 | import { buildOriginallyPublishedAt, safeGetYoutubeDL } from '../helpers/youtube-dl' |
14 | import { buildCommonVideoOptions, buildVideoAttributesFromCommander, getServerCredentials } from './cli' | 15 | import { buildCommonVideoOptions, buildVideoAttributesFromCommander, getServerCredentials, getLogger } from './cli' |
15 | 16 | ||
16 | type UserInfo = { | 17 | type UserInfo = { |
17 | username: string | 18 | username: string |
@@ -19,7 +20,6 @@ type UserInfo = { | |||
19 | } | 20 | } |
20 | 21 | ||
21 | const processOptions = { | 22 | const processOptions = { |
22 | cwd: __dirname, | ||
23 | maxBuffer: Infinity | 23 | maxBuffer: Infinity |
24 | } | 24 | } |
25 | 25 | ||
@@ -35,15 +35,23 @@ command | |||
35 | .option('--target-url <targetUrl>', 'Video target URL') | 35 | .option('--target-url <targetUrl>', 'Video target URL') |
36 | .option('--since <since>', 'Publication date (inclusive) since which the videos can be imported (YYYY-MM-DD)', parseDate) | 36 | .option('--since <since>', 'Publication date (inclusive) since which the videos can be imported (YYYY-MM-DD)', parseDate) |
37 | .option('--until <until>', 'Publication date (inclusive) until which the videos can be imported (YYYY-MM-DD)', parseDate) | 37 | .option('--until <until>', 'Publication date (inclusive) until which the videos can be imported (YYYY-MM-DD)', parseDate) |
38 | .option('-v, --verbose', 'Verbose mode') | 38 | .option('--first <first>', 'Process first n elements of returned playlist') |
39 | .option('--last <last>', 'Process last n elements of returned playlist') | ||
40 | .option('-T, --tmpdir <tmpdir>', 'Working directory', __dirname) | ||
39 | .parse(process.argv) | 41 | .parse(process.argv) |
40 | 42 | ||
43 | let log = getLogger(program[ 'verbose' ]) | ||
44 | |||
41 | getServerCredentials(command) | 45 | getServerCredentials(command) |
42 | .then(({ url, username, password }) => { | 46 | .then(({ url, username, password }) => { |
43 | if (!program[ 'targetUrl' ]) { | 47 | if (!program[ 'targetUrl' ]) { |
44 | console.error('--targetUrl field is required.') | 48 | exitError('--target-url field is required.') |
49 | } | ||
45 | 50 | ||
46 | process.exit(-1) | 51 | try { |
52 | accessSync(program[ 'tmpdir' ], constants.R_OK | constants.W_OK) | ||
53 | } catch (e) { | ||
54 | exitError('--tmpdir %s: directory does not exist or is not accessible', program[ 'tmpdir' ]) | ||
47 | } | 55 | } |
48 | 56 | ||
49 | removeEndSlashes(url) | 57 | removeEndSlashes(url) |
@@ -53,8 +61,7 @@ getServerCredentials(command) | |||
53 | 61 | ||
54 | run(url, user) | 62 | run(url, user) |
55 | .catch(err => { | 63 | .catch(err => { |
56 | console.error(err) | 64 | exitError(err) |
57 | process.exit(-1) | ||
58 | }) | 65 | }) |
59 | }) | 66 | }) |
60 | 67 | ||
@@ -68,30 +75,32 @@ async function run (url: string, user: UserInfo) { | |||
68 | const options = [ '-j', '--flat-playlist', '--playlist-reverse' ] | 75 | const options = [ '-j', '--flat-playlist', '--playlist-reverse' ] |
69 | youtubeDL.getInfo(program[ 'targetUrl' ], options, processOptions, async (err, info) => { | 76 | youtubeDL.getInfo(program[ 'targetUrl' ], options, processOptions, async (err, info) => { |
70 | if (err) { | 77 | if (err) { |
71 | console.log(err.message) | 78 | exitError(err.message) |
72 | process.exit(1) | ||
73 | } | 79 | } |
74 | 80 | ||
75 | let infoArray: any[] | 81 | let infoArray: any[] |
76 | 82 | ||
77 | // Normalize utf8 fields | 83 | // Normalize utf8 fields |
78 | if (Array.isArray(info) === true) { | 84 | infoArray = [].concat(info); |
79 | infoArray = info.map(i => normalizeObject(i)) | 85 | if (program[ 'first' ]) { |
80 | } else { | 86 | infoArray = infoArray.slice(0, program[ 'first' ]) |
81 | infoArray = [ normalizeObject(info) ] | 87 | } else if (program[ 'last' ]) { |
88 | infoArray = infoArray.slice(- program[ 'last' ]) | ||
82 | } | 89 | } |
83 | console.log('Will download and upload %d videos.\n', infoArray.length) | 90 | infoArray = infoArray.map(i => normalizeObject(i)) |
91 | |||
92 | log.info('Will download and upload %d videos.\n', infoArray.length) | ||
84 | 93 | ||
85 | for (const info of infoArray) { | 94 | for (const info of infoArray) { |
86 | await processVideo({ | 95 | await processVideo({ |
87 | cwd: processOptions.cwd, | 96 | cwd: program[ 'tmpdir' ], |
88 | url, | 97 | url, |
89 | user, | 98 | user, |
90 | youtubeInfo: info | 99 | youtubeInfo: info |
91 | }) | 100 | }) |
92 | } | 101 | } |
93 | 102 | ||
94 | console.log('Video/s for user %s imported: %s', program[ 'username' ], program[ 'targetUrl' ]) | 103 | log.info('Video/s for user %s imported: %s', user.username, program[ 'targetUrl' ]) |
95 | process.exit(0) | 104 | process.exit(0) |
96 | }) | 105 | }) |
97 | } | 106 | } |
@@ -105,21 +114,21 @@ function processVideo (parameters: { | |||
105 | const { youtubeInfo, cwd, url, user } = parameters | 114 | const { youtubeInfo, cwd, url, user } = parameters |
106 | 115 | ||
107 | return new Promise(async res => { | 116 | return new Promise(async res => { |
108 | if (program[ 'verbose' ]) console.log('Fetching object.', youtubeInfo) | 117 | log.debug('Fetching object.', youtubeInfo) |
109 | 118 | ||
110 | const videoInfo = await fetchObject(youtubeInfo) | 119 | const videoInfo = await fetchObject(youtubeInfo) |
111 | if (program[ 'verbose' ]) console.log('Fetched object.', videoInfo) | 120 | log.debug('Fetched object.', videoInfo) |
112 | 121 | ||
113 | if (program[ 'since' ]) { | 122 | if (program[ 'since' ]) { |
114 | if (buildOriginallyPublishedAt(videoInfo).getTime() < program[ 'since' ].getTime()) { | 123 | if (buildOriginallyPublishedAt(videoInfo).getTime() < program[ 'since' ].getTime()) { |
115 | console.log('Video "%s" has been published before "%s", don\'t upload it.\n', | 124 | log.info('Video "%s" has been published before "%s", don\'t upload it.\n', |
116 | videoInfo.title, formatDate(program[ 'since' ])); | 125 | videoInfo.title, formatDate(program[ 'since' ])); |
117 | return res(); | 126 | return res(); |
118 | } | 127 | } |
119 | } | 128 | } |
120 | if (program[ 'until' ]) { | 129 | if (program[ 'until' ]) { |
121 | if (buildOriginallyPublishedAt(videoInfo).getTime() > program[ 'until' ].getTime()) { | 130 | if (buildOriginallyPublishedAt(videoInfo).getTime() > program[ 'until' ].getTime()) { |
122 | console.log('Video "%s" has been published after "%s", don\'t upload it.\n', | 131 | log.info('Video "%s" has been published after "%s", don\'t upload it.\n', |
123 | videoInfo.title, formatDate(program[ 'until' ])); | 132 | videoInfo.title, formatDate(program[ 'until' ])); |
124 | return res(); | 133 | return res(); |
125 | } | 134 | } |
@@ -127,27 +136,27 @@ function processVideo (parameters: { | |||
127 | 136 | ||
128 | const result = await searchVideoWithSort(url, videoInfo.title, '-match') | 137 | const result = await searchVideoWithSort(url, videoInfo.title, '-match') |
129 | 138 | ||
130 | console.log('############################################################\n') | 139 | log.info('############################################################\n') |
131 | 140 | ||
132 | if (result.body.data.find(v => v.name === videoInfo.title)) { | 141 | if (result.body.data.find(v => v.name === videoInfo.title)) { |
133 | console.log('Video "%s" already exists, don\'t reupload it.\n', videoInfo.title) | 142 | log.info('Video "%s" already exists, don\'t reupload it.\n', videoInfo.title) |
134 | return res() | 143 | return res() |
135 | } | 144 | } |
136 | 145 | ||
137 | const path = join(cwd, sha256(videoInfo.url) + '.mp4') | 146 | const path = join(cwd, sha256(videoInfo.url) + '.mp4') |
138 | 147 | ||
139 | console.log('Downloading video "%s"...', videoInfo.title) | 148 | log.info('Downloading video "%s"...', videoInfo.title) |
140 | 149 | ||
141 | const options = [ '-f', 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best', '-o', path ] | 150 | const options = [ '-f', 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best', '-o', path ] |
142 | try { | 151 | try { |
143 | const youtubeDL = await safeGetYoutubeDL() | 152 | const youtubeDL = await safeGetYoutubeDL() |
144 | youtubeDL.exec(videoInfo.url, options, processOptions, async (err, output) => { | 153 | youtubeDL.exec(videoInfo.url, options, processOptions, async (err, output) => { |
145 | if (err) { | 154 | if (err) { |
146 | console.error(err) | 155 | log.error(err) |
147 | return res() | 156 | return res() |
148 | } | 157 | } |
149 | 158 | ||
150 | console.log(output.join('\n')) | 159 | log.info(output.join('\n')) |
151 | await uploadVideoOnPeerTube({ | 160 | await uploadVideoOnPeerTube({ |
152 | cwd, | 161 | cwd, |
153 | url, | 162 | url, |
@@ -158,7 +167,7 @@ function processVideo (parameters: { | |||
158 | return res() | 167 | return res() |
159 | }) | 168 | }) |
160 | } catch (err) { | 169 | } catch (err) { |
161 | console.log(err.message) | 170 | log.error(err.message) |
162 | return res() | 171 | return res() |
163 | } | 172 | } |
164 | }) | 173 | }) |
@@ -217,7 +226,7 @@ async function uploadVideoOnPeerTube (parameters: { | |||
217 | fixture: videoPath | 226 | fixture: videoPath |
218 | }) | 227 | }) |
219 | 228 | ||
220 | console.log('\nUploading on PeerTube video "%s".', videoAttributes.name) | 229 | log.info('\nUploading on PeerTube video "%s".', videoAttributes.name) |
221 | 230 | ||
222 | let accessToken = await getAccessTokenOrDie(url, user) | 231 | let accessToken = await getAccessTokenOrDie(url, user) |
223 | 232 | ||
@@ -225,21 +234,20 @@ async function uploadVideoOnPeerTube (parameters: { | |||
225 | await uploadVideo(url, accessToken, videoAttributes) | 234 | await uploadVideo(url, accessToken, videoAttributes) |
226 | } catch (err) { | 235 | } catch (err) { |
227 | if (err.message.indexOf('401') !== -1) { | 236 | if (err.message.indexOf('401') !== -1) { |
228 | console.log('Got 401 Unauthorized, token may have expired, renewing token and retry.') | 237 | log.info('Got 401 Unauthorized, token may have expired, renewing token and retry.') |
229 | 238 | ||
230 | accessToken = await getAccessTokenOrDie(url, user) | 239 | accessToken = await getAccessTokenOrDie(url, user) |
231 | 240 | ||
232 | await uploadVideo(url, accessToken, videoAttributes) | 241 | await uploadVideo(url, accessToken, videoAttributes) |
233 | } else { | 242 | } else { |
234 | console.log(err.message) | 243 | exitError(err.message) |
235 | process.exit(1) | ||
236 | } | 244 | } |
237 | } | 245 | } |
238 | 246 | ||
239 | await remove(videoPath) | 247 | await remove(videoPath) |
240 | if (thumbnailfile) await remove(thumbnailfile) | 248 | if (thumbnailfile) await remove(thumbnailfile) |
241 | 249 | ||
242 | console.log('Uploaded video "%s"!\n', videoAttributes.name) | 250 | log.warn('Uploaded video "%s"!\n', videoAttributes.name) |
243 | } | 251 | } |
244 | 252 | ||
245 | /* ---------------------------------------------------------- */ | 253 | /* ---------------------------------------------------------- */ |
@@ -355,20 +363,17 @@ async function getAccessTokenOrDie (url: string, user: UserInfo) { | |||
355 | const res = await login(url, client, user) | 363 | const res = await login(url, client, user) |
356 | return res.body.access_token | 364 | return res.body.access_token |
357 | } catch (err) { | 365 | } catch (err) { |
358 | console.error('Cannot authenticate. Please check your username/password.') | 366 | exitError('Cannot authenticate. Please check your username/password.') |
359 | process.exit(-1) | ||
360 | } | 367 | } |
361 | } | 368 | } |
362 | 369 | ||
363 | function parseDate (dateAsStr: string): Date { | 370 | function parseDate (dateAsStr: string): Date { |
364 | if (!/\d{4}-\d{2}-\d{2}/.test(dateAsStr)) { | 371 | if (!/\d{4}-\d{2}-\d{2}/.test(dateAsStr)) { |
365 | console.error(`Invalid date passed: ${dateAsStr}. Expected format: YYYY-MM-DD. See help for usage.`); | 372 | exitError(`Invalid date passed: ${dateAsStr}. Expected format: YYYY-MM-DD. See help for usage.`); |
366 | process.exit(-1); | ||
367 | } | 373 | } |
368 | const date = new Date(dateAsStr); | 374 | const date = new Date(dateAsStr); |
369 | if (isNaN(date.getTime())) { | 375 | if (isNaN(date.getTime())) { |
370 | console.error(`Invalid date passed: ${dateAsStr}. See help for usage.`); | 376 | exitError(`Invalid date passed: ${dateAsStr}. See help for usage.`); |
371 | process.exit(-1); | ||
372 | } | 377 | } |
373 | return date; | 378 | return date; |
374 | } | 379 | } |
@@ -376,3 +381,9 @@ function parseDate (dateAsStr: string): Date { | |||
376 | function formatDate (date: Date): string { | 381 | function formatDate (date: Date): string { |
377 | return date.toISOString().split('T')[0]; | 382 | return date.toISOString().split('T')[0]; |
378 | } | 383 | } |
384 | |||
385 | function exitError (message:string, ...meta: any[]) { | ||
386 | // use console.error instead of log.error here | ||
387 | console.error(message, ...meta) | ||
388 | process.exit(-1) | ||
389 | } | ||
diff --git a/server/typings/activitypub-processor.model.ts b/server/typings/activitypub-processor.model.ts index 37b2859de..7ed3a65b1 100644 --- a/server/typings/activitypub-processor.model.ts +++ b/server/typings/activitypub-processor.model.ts | |||
@@ -1,10 +1,9 @@ | |||
1 | import { Activity } from '../../shared/models/activitypub' | 1 | import { Activity } from '../../shared/models/activitypub' |
2 | import { ActorModel } from '../models/activitypub/actor' | 2 | import { MActorDefault, MActorSignature } from './models' |
3 | import { SignatureActorModel } from './models' | ||
4 | 3 | ||
5 | export type APProcessorOptions<T extends Activity> = { | 4 | export type APProcessorOptions<T extends Activity> = { |
6 | activity: T | 5 | activity: T |
7 | byActor: SignatureActorModel | 6 | byActor: MActorSignature |
8 | inboxActor?: ActorModel | 7 | inboxActor?: MActorDefault |
9 | fromFetch?: boolean | 8 | fromFetch?: boolean |
10 | } | 9 | } |
diff --git a/server/typings/express.ts b/server/typings/express.ts index f7da55ab0..3cc7c7632 100644 --- a/server/typings/express.ts +++ b/server/typings/express.ts | |||
@@ -1,89 +1,103 @@ | |||
1 | import { VideoChannelModel } from '../models/video/video-channel' | ||
2 | import { VideoPlaylistModel } from '../models/video/video-playlist' | ||
3 | import { VideoPlaylistElementModel } from '../models/video/video-playlist-element' | ||
4 | import { UserModel } from '../models/account/user' | ||
5 | import { VideoModel } from '../models/video/video' | ||
6 | import { AccountModel } from '../models/account/account' | ||
7 | import { VideoChangeOwnershipModel } from '../models/video/video-change-ownership' | ||
8 | import { ActorModel } from '../models/activitypub/actor' | ||
9 | import { VideoCommentModel } from '../models/video/video-comment' | ||
10 | import { VideoShareModel } from '../models/video/video-share' | ||
11 | import { AccountVideoRateModel } from '../models/account/account-video-rate' | ||
12 | import { ActorFollowModel } from '../models/activitypub/actor-follow' | ||
13 | import { ServerModel } from '../models/server/server' | ||
14 | import { VideoFileModel } from '../models/video/video-file' | ||
15 | import { VideoRedundancyModel } from '../models/redundancy/video-redundancy' | ||
16 | import { ServerBlocklistModel } from '../models/server/server-blocklist' | ||
17 | import { AccountBlocklistModel } from '../models/account/account-blocklist' | ||
18 | import { VideoImportModel } from '../models/video/video-import' | ||
19 | import { VideoAbuseModel } from '../models/video/video-abuse' | ||
20 | import { VideoBlacklistModel } from '../models/video/video-blacklist' | ||
21 | import { VideoCaptionModel } from '../models/video/video-caption' | ||
22 | import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist' | ||
23 | import { RegisteredPlugin } from '../lib/plugins/plugin-manager' | 1 | import { RegisteredPlugin } from '../lib/plugins/plugin-manager' |
24 | import { PluginModel } from '../models/server/plugin' | 2 | import { |
25 | import { SignatureActorModel } from './models' | 3 | MAccountDefault, |
4 | MActorAccountChannelId, | ||
5 | MActorFollowActorsDefault, | ||
6 | MActorFollowActorsDefaultSubscription, | ||
7 | MActorFull, | ||
8 | MChannelAccountDefault, | ||
9 | MComment, | ||
10 | MCommentOwnerVideoReply, | ||
11 | MUserDefault, | ||
12 | MVideoAbuse, | ||
13 | MVideoBlacklist, | ||
14 | MVideoCaptionVideo, | ||
15 | MVideoFullLight, | ||
16 | MVideoIdThumbnail, | ||
17 | MVideoRedundancyVideo, | ||
18 | MVideoShareActor, | ||
19 | MVideoThumbnail, | ||
20 | MVideoWithRights | ||
21 | } from './models' | ||
22 | import { MVideoPlaylistFull, MVideoPlaylistFullSummary } from './models/video/video-playlist' | ||
23 | import { MVideoImportDefault } from '@server/typings/models/video/video-import' | ||
24 | import { MAccountBlocklist, MStreamingPlaylist, MVideoFile } from '@server/typings/models' | ||
25 | import { MVideoPlaylistElement, MVideoPlaylistElementVideoUrlPlaylistPrivacy } from '@server/typings/models/video/video-playlist-element' | ||
26 | import { MAccountVideoRateAccountVideo } from '@server/typings/models/video/video-rate' | ||
27 | import { MVideoChangeOwnershipFull } from './models/video/video-change-ownership' | ||
28 | import { MPlugin, MServer } from '@server/typings/models/server' | ||
29 | import { MServerBlocklist } from './models/server/server-blocklist' | ||
30 | import { MOAuthTokenUser } from '@server/typings/models/oauth/oauth-token' | ||
26 | 31 | ||
27 | declare module 'express' { | 32 | declare module 'express' { |
28 | 33 | ||
29 | interface Response { | 34 | interface Response { |
35 | |||
30 | locals: { | 36 | locals: { |
31 | video?: VideoModel | 37 | videoAll?: MVideoFullLight |
32 | videoShare?: VideoShareModel | 38 | onlyVideo?: MVideoThumbnail |
33 | videoFile?: VideoFileModel | 39 | onlyVideoWithRights?: MVideoWithRights |
40 | videoId?: MVideoIdThumbnail | ||
41 | |||
42 | videoShare?: MVideoShareActor | ||
43 | |||
44 | videoFile?: MVideoFile | ||
45 | |||
46 | videoImport?: MVideoImportDefault | ||
47 | |||
48 | videoBlacklist?: MVideoBlacklist | ||
49 | |||
50 | videoCaption?: MVideoCaptionVideo | ||
51 | |||
52 | videoAbuse?: MVideoAbuse | ||
34 | 53 | ||
35 | videoImport?: VideoImportModel | 54 | videoStreamingPlaylist?: MStreamingPlaylist |
36 | 55 | ||
37 | videoBlacklist?: VideoBlacklistModel | 56 | videoChannel?: MChannelAccountDefault |
38 | 57 | ||
39 | videoCaption?: VideoCaptionModel | 58 | videoPlaylistFull?: MVideoPlaylistFull |
59 | videoPlaylistSummary?: MVideoPlaylistFullSummary | ||
40 | 60 | ||
41 | videoAbuse?: VideoAbuseModel | 61 | videoPlaylistElement?: MVideoPlaylistElement |
62 | videoPlaylistElementAP?: MVideoPlaylistElementVideoUrlPlaylistPrivacy | ||
42 | 63 | ||
43 | videoStreamingPlaylist?: VideoStreamingPlaylistModel | 64 | accountVideoRate?: MAccountVideoRateAccountVideo |
44 | 65 | ||
45 | videoChannel?: VideoChannelModel | 66 | videoCommentFull?: MCommentOwnerVideoReply |
67 | videoCommentThread?: MComment | ||
46 | 68 | ||
47 | videoPlaylist?: VideoPlaylistModel | 69 | follow?: MActorFollowActorsDefault |
48 | videoPlaylistElement?: VideoPlaylistElementModel | 70 | subscription?: MActorFollowActorsDefaultSubscription |
49 | 71 | ||
50 | accountVideoRate?: AccountVideoRateModel | 72 | nextOwner?: MAccountDefault |
73 | videoChangeOwnership?: MVideoChangeOwnershipFull | ||
51 | 74 | ||
52 | videoComment?: VideoCommentModel | 75 | account?: MAccountDefault |
53 | videoCommentThread?: VideoCommentModel | ||
54 | 76 | ||
55 | follow?: ActorFollowModel | 77 | actorFull?: MActorFull |
56 | subscription?: ActorFollowModel | ||
57 | 78 | ||
58 | nextOwner?: AccountModel | 79 | user?: MUserDefault |
59 | videoChangeOwnership?: VideoChangeOwnershipModel | ||
60 | account?: AccountModel | ||
61 | actor?: ActorModel | ||
62 | user?: UserModel | ||
63 | 80 | ||
64 | server?: ServerModel | 81 | server?: MServer |
65 | 82 | ||
66 | videoRedundancy?: VideoRedundancyModel | 83 | videoRedundancy?: MVideoRedundancyVideo |
67 | 84 | ||
68 | accountBlock?: AccountBlocklistModel | 85 | accountBlock?: MAccountBlocklist |
69 | serverBlock?: ServerBlocklistModel | 86 | serverBlock?: MServerBlocklist |
70 | 87 | ||
71 | oauth?: { | 88 | oauth?: { |
72 | token: { | 89 | token: MOAuthTokenUser |
73 | User: UserModel | ||
74 | user: UserModel | ||
75 | } | ||
76 | } | 90 | } |
77 | 91 | ||
78 | signature?: { | 92 | signature?: { |
79 | actor: SignatureActorModel | 93 | actor: MActorAccountChannelId |
80 | } | 94 | } |
81 | 95 | ||
82 | authenticated?: boolean | 96 | authenticated?: boolean |
83 | 97 | ||
84 | registeredPlugin?: RegisteredPlugin | 98 | registeredPlugin?: RegisteredPlugin |
85 | 99 | ||
86 | plugin?: PluginModel | 100 | plugin?: MPlugin |
87 | } | 101 | } |
88 | } | 102 | } |
89 | } | 103 | } |
diff --git a/server/typings/models/account/account-blocklist.ts b/server/typings/models/account/account-blocklist.ts new file mode 100644 index 000000000..c9cb55332 --- /dev/null +++ b/server/typings/models/account/account-blocklist.ts | |||
@@ -0,0 +1,25 @@ | |||
1 | import { AccountBlocklistModel } from '../../../models/account/account-blocklist' | ||
2 | import { PickWith } from '../../utils' | ||
3 | import { MAccountDefault, MAccountFormattable } from './account' | ||
4 | |||
5 | type Use<K extends keyof AccountBlocklistModel, M> = PickWith<AccountBlocklistModel, K, M> | ||
6 | |||
7 | // ############################################################################ | ||
8 | |||
9 | export type MAccountBlocklist = Omit<AccountBlocklistModel, 'ByAccount' | 'BlockedAccount'> | ||
10 | |||
11 | // ############################################################################ | ||
12 | |||
13 | export type MAccountBlocklistId = Pick<AccountBlocklistModel, 'id'> | ||
14 | |||
15 | export type MAccountBlocklistAccounts = MAccountBlocklist & | ||
16 | Use<'ByAccount', MAccountDefault> & | ||
17 | Use<'BlockedAccount', MAccountDefault> | ||
18 | |||
19 | // ############################################################################ | ||
20 | |||
21 | // Format for API or AP object | ||
22 | |||
23 | export type MAccountBlocklistFormattable = Pick<MAccountBlocklist, 'createdAt'> & | ||
24 | Use<'ByAccount', MAccountFormattable> & | ||
25 | Use<'BlockedAccount', MAccountFormattable> | ||
diff --git a/server/typings/models/account/account.ts b/server/typings/models/account/account.ts new file mode 100644 index 000000000..ec78fece8 --- /dev/null +++ b/server/typings/models/account/account.ts | |||
@@ -0,0 +1,95 @@ | |||
1 | import { AccountModel } from '../../../models/account/account' | ||
2 | import { | ||
3 | MActor, | ||
4 | MActorAP, | ||
5 | MActorAPI, | ||
6 | MActorAudience, | ||
7 | MActorDefault, | ||
8 | MActorDefaultLight, | ||
9 | MActorFormattable, | ||
10 | MActorId, | ||
11 | MActorServer, | ||
12 | MActorSummary, | ||
13 | MActorSummaryFormattable, | ||
14 | MActorUrl | ||
15 | } from './actor' | ||
16 | import { FunctionProperties, PickWith } from '../../utils' | ||
17 | import { MAccountBlocklistId } from './account-blocklist' | ||
18 | import { MChannelDefault } from '@server/typings/models' | ||
19 | |||
20 | type Use<K extends keyof AccountModel, M> = PickWith<AccountModel, K, M> | ||
21 | |||
22 | // ############################################################################ | ||
23 | |||
24 | export type MAccount = Omit<AccountModel, 'Actor' | 'User' | 'Application' | 'VideoChannels' | 'VideoPlaylists' | | ||
25 | 'VideoComments' | 'BlockedAccounts'> | ||
26 | |||
27 | // ############################################################################ | ||
28 | |||
29 | // Only some attributes | ||
30 | export type MAccountId = Pick<MAccount, 'id'> | ||
31 | export type MAccountUserId = Pick<MAccount, 'userId'> | ||
32 | |||
33 | // Only some Actor attributes | ||
34 | export type MAccountUrl = Use<'Actor', MActorUrl> | ||
35 | export type MAccountAudience = Use<'Actor', MActorAudience> | ||
36 | |||
37 | export type MAccountIdActor = MAccountId & | ||
38 | Use<'Actor', MActor> | ||
39 | |||
40 | export type MAccountIdActorId = MAccountId & | ||
41 | Use<'Actor', MActorId> | ||
42 | |||
43 | // ############################################################################ | ||
44 | |||
45 | // Default scope | ||
46 | export type MAccountDefault = MAccount & | ||
47 | Use<'Actor', MActorDefault> | ||
48 | |||
49 | // Default with default association scopes | ||
50 | export type MAccountDefaultChannelDefault = MAccount & | ||
51 | Use<'Actor', MActorDefault> & | ||
52 | Use<'VideoChannels', MChannelDefault[]> | ||
53 | |||
54 | // We don't need some actors attributes | ||
55 | export type MAccountLight = MAccount & | ||
56 | Use<'Actor', MActorDefaultLight> | ||
57 | |||
58 | // ############################################################################ | ||
59 | |||
60 | // Full actor | ||
61 | export type MAccountActor = MAccount & | ||
62 | Use<'Actor', MActor> | ||
63 | |||
64 | // Full actor with server | ||
65 | export type MAccountServer = MAccount & | ||
66 | Use<'Actor', MActorServer> | ||
67 | |||
68 | // ############################################################################ | ||
69 | |||
70 | // For API | ||
71 | |||
72 | export type MAccountSummary = FunctionProperties<MAccount> & | ||
73 | Pick<MAccount, 'id' | 'name'> & | ||
74 | Use<'Actor', MActorSummary> | ||
75 | |||
76 | export type MAccountSummaryBlocks = MAccountSummary & | ||
77 | Use<'BlockedAccounts', MAccountBlocklistId[]> | ||
78 | |||
79 | export type MAccountAPI = MAccount & | ||
80 | Use<'Actor', MActorAPI> | ||
81 | |||
82 | // ############################################################################ | ||
83 | |||
84 | // Format for API or AP object | ||
85 | |||
86 | export type MAccountSummaryFormattable = FunctionProperties<MAccount> & | ||
87 | Pick<MAccount, 'id' | 'name'> & | ||
88 | Use<'Actor', MActorSummaryFormattable> | ||
89 | |||
90 | export type MAccountFormattable = FunctionProperties<MAccount> & | ||
91 | Pick<MAccount, 'id' | 'name' | 'description' | 'createdAt' | 'updatedAt' | 'userId'> & | ||
92 | Use<'Actor', MActorFormattable> | ||
93 | |||
94 | export type MAccountAP = Pick<MAccount, 'name' | 'description'> & | ||
95 | Use<'Actor', MActorAP> | ||
diff --git a/server/typings/models/account/actor-follow.ts b/server/typings/models/account/actor-follow.ts new file mode 100644 index 000000000..1c66eb0a0 --- /dev/null +++ b/server/typings/models/account/actor-follow.ts | |||
@@ -0,0 +1,63 @@ | |||
1 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' | ||
2 | import { | ||
3 | MActor, | ||
4 | MActorAccount, | ||
5 | MActorDefaultAccountChannel, | ||
6 | MActorChannelAccountActor, | ||
7 | MActorDefault, | ||
8 | MActorFormattable, | ||
9 | MActorHost, | ||
10 | MActorUsername | ||
11 | } from './actor' | ||
12 | import { PickWith } from '../../utils' | ||
13 | import { ActorModel } from '@server/models/activitypub/actor' | ||
14 | import { MChannelDefault } from '@server/typings/models' | ||
15 | |||
16 | type Use<K extends keyof ActorFollowModel, M> = PickWith<ActorFollowModel, K, M> | ||
17 | |||
18 | // ############################################################################ | ||
19 | |||
20 | export type MActorFollow = Omit<ActorFollowModel, 'ActorFollower' | 'ActorFollowing'> | ||
21 | |||
22 | // ############################################################################ | ||
23 | |||
24 | export type MActorFollowFollowingHost = MActorFollow & | ||
25 | Use<'ActorFollowing', MActorUsername & MActorHost> | ||
26 | |||
27 | // ############################################################################ | ||
28 | |||
29 | // With actors or actors default | ||
30 | |||
31 | export type MActorFollowActors = MActorFollow & | ||
32 | Use<'ActorFollower', MActor> & | ||
33 | Use<'ActorFollowing', MActor> | ||
34 | |||
35 | export type MActorFollowActorsDefault = MActorFollow & | ||
36 | Use<'ActorFollower', MActorDefault> & | ||
37 | Use<'ActorFollowing', MActorDefault> | ||
38 | |||
39 | export type MActorFollowFull = MActorFollow & | ||
40 | Use<'ActorFollower', MActorDefaultAccountChannel> & | ||
41 | Use<'ActorFollowing', MActorDefaultAccountChannel> | ||
42 | |||
43 | // ############################################################################ | ||
44 | |||
45 | // For subscriptions | ||
46 | |||
47 | type SubscriptionFollowing = MActorDefault & | ||
48 | PickWith<ActorModel, 'VideoChannel', MChannelDefault> | ||
49 | |||
50 | export type MActorFollowActorsDefaultSubscription = MActorFollow & | ||
51 | Use<'ActorFollower', MActorDefault> & | ||
52 | Use<'ActorFollowing', SubscriptionFollowing> | ||
53 | |||
54 | export type MActorFollowSubscriptions = MActorFollow & | ||
55 | Use<'ActorFollowing', MActorChannelAccountActor> | ||
56 | |||
57 | // ############################################################################ | ||
58 | |||
59 | // Format for API or AP object | ||
60 | |||
61 | export type MActorFollowFormattable = Pick<MActorFollow, 'id' | 'score' | 'state' | 'createdAt' | 'updatedAt'> & | ||
62 | Use<'ActorFollower', MActorFormattable> & | ||
63 | Use<'ActorFollowing', MActorFormattable> | ||
diff --git a/server/typings/models/account/actor.ts b/server/typings/models/account/actor.ts new file mode 100644 index 000000000..bcacb8351 --- /dev/null +++ b/server/typings/models/account/actor.ts | |||
@@ -0,0 +1,121 @@ | |||
1 | import { ActorModel } from '../../../models/activitypub/actor' | ||
2 | import { FunctionProperties, PickWith, PickWithOpt } from '../../utils' | ||
3 | import { MAccount, MAccountDefault, MAccountId, MAccountIdActor } from './account' | ||
4 | import { MServer, MServerHost, MServerHostBlocks, MServerRedundancyAllowed } from '../server' | ||
5 | import { MAvatar, MAvatarFormattable } from './avatar' | ||
6 | import { MChannel, MChannelAccountActor, MChannelAccountDefault, MChannelId, MChannelIdActor } from '../video' | ||
7 | |||
8 | type Use<K extends keyof ActorModel, M> = PickWith<ActorModel, K, M> | ||
9 | |||
10 | // ############################################################################ | ||
11 | |||
12 | export type MActor = Omit<ActorModel, 'Account' | 'VideoChannel' | 'ActorFollowing' | 'Avatar' | 'ActorFollowers' | 'Server'> | ||
13 | |||
14 | // ############################################################################ | ||
15 | |||
16 | export type MActorUrl = Pick<MActor, 'url'> | ||
17 | export type MActorId = Pick<MActor, 'id'> | ||
18 | export type MActorUsername = Pick<MActor, 'preferredUsername'> | ||
19 | |||
20 | export type MActorFollowersUrl = Pick<MActor, 'followersUrl'> | ||
21 | export type MActorAudience = MActorUrl & MActorFollowersUrl | ||
22 | export type MActorFollowerException = Pick<ActorModel, 'sharedInboxUrl' | 'inboxUrl'> | ||
23 | export type MActorSignature = MActorAccountChannelId | ||
24 | |||
25 | export type MActorLight = Omit<MActor, 'privateKey' | 'privateKey'> | ||
26 | |||
27 | // ############################################################################ | ||
28 | |||
29 | // Some association attributes | ||
30 | |||
31 | export type MActorHost = Use<'Server', MServerHost> | ||
32 | export type MActorRedundancyAllowedOpt = PickWithOpt<ActorModel, 'Server', MServerRedundancyAllowed> | ||
33 | |||
34 | export type MActorDefaultLight = MActorLight & | ||
35 | Use<'Server', MServerHost> & | ||
36 | Use<'Avatar', MAvatar> | ||
37 | |||
38 | export type MActorAccountId = MActor & | ||
39 | Use<'Account', MAccountId> | ||
40 | export type MActorAccountIdActor = MActor & | ||
41 | Use<'Account', MAccountIdActor> | ||
42 | |||
43 | export type MActorChannelId = MActor & | ||
44 | Use<'VideoChannel', MChannelId> | ||
45 | export type MActorChannelIdActor = MActor & | ||
46 | Use<'VideoChannel', MChannelIdActor> | ||
47 | |||
48 | export type MActorAccountChannelId = MActorAccountId & MActorChannelId | ||
49 | export type MActorAccountChannelIdActor = MActorAccountIdActor & MActorChannelIdActor | ||
50 | |||
51 | // ############################################################################ | ||
52 | |||
53 | // Include raw account/channel/server | ||
54 | |||
55 | export type MActorAccount = MActor & | ||
56 | Use<'Account', MAccount> | ||
57 | |||
58 | export type MActorChannel = MActor & | ||
59 | Use<'VideoChannel', MChannel> | ||
60 | |||
61 | export type MActorDefaultAccountChannel = MActorDefault & MActorAccount & MActorChannel | ||
62 | |||
63 | export type MActorServer = MActor & | ||
64 | Use<'Server', MServer> | ||
65 | |||
66 | // ############################################################################ | ||
67 | |||
68 | // Complex actor associations | ||
69 | |||
70 | export type MActorDefault = MActor & | ||
71 | Use<'Server', MServer> & | ||
72 | Use<'Avatar', MAvatar> | ||
73 | |||
74 | // Actor with channel that is associated to an account and its actor | ||
75 | // Actor -> VideoChannel -> Account -> Actor | ||
76 | export type MActorChannelAccountActor = MActor & | ||
77 | Use<'VideoChannel', MChannelAccountActor> | ||
78 | |||
79 | export type MActorFull = MActor & | ||
80 | Use<'Server', MServer> & | ||
81 | Use<'Avatar', MAvatar> & | ||
82 | Use<'Account', MAccount> & | ||
83 | Use<'VideoChannel', MChannelAccountActor> | ||
84 | |||
85 | // Same than ActorFull, but the account and the channel have their actor | ||
86 | export type MActorFullActor = MActor & | ||
87 | Use<'Server', MServer> & | ||
88 | Use<'Avatar', MAvatar> & | ||
89 | Use<'Account', MAccountDefault> & | ||
90 | Use<'VideoChannel', MChannelAccountDefault> | ||
91 | |||
92 | // ############################################################################ | ||
93 | |||
94 | // API | ||
95 | |||
96 | export type MActorSummary = FunctionProperties<MActor> & | ||
97 | Pick<MActor, 'id' | 'preferredUsername' | 'url' | 'serverId' | 'avatarId'> & | ||
98 | Use<'Server', MServerHost> & | ||
99 | Use<'Avatar', MAvatar> | ||
100 | |||
101 | export type MActorSummaryBlocks = MActorSummary & | ||
102 | Use<'Server', MServerHostBlocks> | ||
103 | |||
104 | export type MActorAPI = Omit<MActorDefault, 'publicKey' | 'privateKey' | 'inboxUrl' | 'outboxUrl' | 'sharedInboxUrl' | | ||
105 | 'followersUrl' | 'followingUrl' | 'url' | 'createdAt' | 'updatedAt'> | ||
106 | |||
107 | // ############################################################################ | ||
108 | |||
109 | // Format for API or AP object | ||
110 | |||
111 | export type MActorSummaryFormattable = FunctionProperties<MActor> & | ||
112 | Pick<MActor, 'url' | 'preferredUsername'> & | ||
113 | Use<'Server', MServerHost> & | ||
114 | Use<'Avatar', MAvatarFormattable> | ||
115 | |||
116 | export type MActorFormattable = MActorSummaryFormattable & | ||
117 | Pick<MActor, 'id' | 'followingCount' | 'followersCount' | 'createdAt' | 'updatedAt'> & | ||
118 | Use<'Server', MServerHost & Partial<Pick<MServer, 'redundancyAllowed'>>> | ||
119 | |||
120 | export type MActorAP = MActor & | ||
121 | Use<'Avatar', MAvatar> | ||
diff --git a/server/typings/models/account/avatar.ts b/server/typings/models/account/avatar.ts new file mode 100644 index 000000000..8af6cc787 --- /dev/null +++ b/server/typings/models/account/avatar.ts | |||
@@ -0,0 +1,11 @@ | |||
1 | import { AvatarModel } from '../../../models/avatar/avatar' | ||
2 | import { FunctionProperties } from '@server/typings/utils' | ||
3 | |||
4 | export type MAvatar = AvatarModel | ||
5 | |||
6 | // ############################################################################ | ||
7 | |||
8 | // Format for API or AP object | ||
9 | |||
10 | export type MAvatarFormattable = FunctionProperties<MAvatar> & | ||
11 | Pick<MAvatar, 'filename' | 'createdAt' | 'updatedAt'> | ||
diff --git a/server/typings/models/account/index.d.ts b/server/typings/models/account/index.d.ts new file mode 100644 index 000000000..513c09c40 --- /dev/null +++ b/server/typings/models/account/index.d.ts | |||
@@ -0,0 +1,5 @@ | |||
1 | export * from './account' | ||
2 | export * from './account-blocklist' | ||
3 | export * from './actor' | ||
4 | export * from './actor-follow' | ||
5 | export * from './avatar' | ||
diff --git a/server/typings/models/actor-follow.ts b/server/typings/models/actor-follow.ts deleted file mode 100644 index 952ef877b..000000000 --- a/server/typings/models/actor-follow.ts +++ /dev/null | |||
@@ -1,8 +0,0 @@ | |||
1 | import { ActorFollowModel } from '../../models/activitypub/actor-follow' | ||
2 | import { ActorModelOnly } from './actor' | ||
3 | |||
4 | export type ActorFollowModelOnly = Omit<ActorFollowModel, 'ActorFollower' | 'ActorFollowing'> | ||
5 | export type ActorFollowModelLight = ActorFollowModelOnly & { | ||
6 | ActorFollower: ActorModelOnly | ||
7 | ActorFollowing: ActorModelOnly | ||
8 | } | ||
diff --git a/server/typings/models/actor.ts b/server/typings/models/actor.ts deleted file mode 100644 index 2656c7b66..000000000 --- a/server/typings/models/actor.ts +++ /dev/null | |||
@@ -1,22 +0,0 @@ | |||
1 | import { ActorModel } from '../../models/activitypub/actor' | ||
2 | import { VideoChannelModel } from '../../models/video/video-channel' | ||
3 | import { AccountModel } from '../../models/account/account' | ||
4 | import { FunctionProperties } from '../utils' | ||
5 | |||
6 | export type VideoChannelModelId = FunctionProperties<VideoChannelModel> | ||
7 | export type AccountModelId = FunctionProperties<AccountModel> | Pick<AccountModel, 'id'> | ||
8 | |||
9 | export type VideoChannelModelIdActor = VideoChannelModelId & Pick<VideoChannelModel, 'Actor'> | ||
10 | export type AccountModelIdActor = AccountModelId & Pick<AccountModel, 'Actor'> | ||
11 | |||
12 | export type ActorModelUrl = Pick<ActorModel, 'url'> | ||
13 | export type ActorModelOnly = Omit<ActorModel, 'Account' | 'VideoChannel' | 'ActorFollowing' | 'Avatar' | 'ActorFollowers' | 'Server'> | ||
14 | export type ActorModelId = Pick<ActorModelOnly, 'id'> | ||
15 | |||
16 | export type SignatureActorModel = ActorModelOnly & { | ||
17 | VideoChannel: VideoChannelModelIdActor | ||
18 | |||
19 | Account: AccountModelIdActor | ||
20 | } | ||
21 | |||
22 | export type ActorFollowerException = Pick<ActorModel, 'sharedInboxUrl' | 'inboxUrl'> | ||
diff --git a/server/typings/models/index.d.ts b/server/typings/models/index.d.ts index c90656965..78b4948ce 100644 --- a/server/typings/models/index.d.ts +++ b/server/typings/models/index.d.ts | |||
@@ -1 +1,5 @@ | |||
1 | export * from './actor' | 1 | export * from './account' |
2 | export * from './oauth' | ||
3 | export * from './server' | ||
4 | export * from './user' | ||
5 | export * from './video' | ||
diff --git a/server/typings/models/oauth/index.d.ts b/server/typings/models/oauth/index.d.ts new file mode 100644 index 000000000..36b7ea8ca --- /dev/null +++ b/server/typings/models/oauth/index.d.ts | |||
@@ -0,0 +1,2 @@ | |||
1 | export * from './oauth-client' | ||
2 | export * from './oauth-token' | ||
diff --git a/server/typings/models/oauth/oauth-client.ts b/server/typings/models/oauth/oauth-client.ts new file mode 100644 index 000000000..904a07863 --- /dev/null +++ b/server/typings/models/oauth/oauth-client.ts | |||
@@ -0,0 +1,3 @@ | |||
1 | import { OAuthClientModel } from '@server/models/oauth/oauth-client' | ||
2 | |||
3 | export type MOAuthClient = Omit<OAuthClientModel, 'OAuthTokens'> | ||
diff --git a/server/typings/models/oauth/oauth-token.ts b/server/typings/models/oauth/oauth-token.ts new file mode 100644 index 000000000..af3412925 --- /dev/null +++ b/server/typings/models/oauth/oauth-token.ts | |||
@@ -0,0 +1,13 @@ | |||
1 | import { OAuthTokenModel } from '@server/models/oauth/oauth-token' | ||
2 | import { PickWith } from '@server/typings/utils' | ||
3 | import { MUserAccountUrl } from '@server/typings/models' | ||
4 | |||
5 | type Use<K extends keyof OAuthTokenModel, M> = PickWith<OAuthTokenModel, K, M> | ||
6 | |||
7 | // ############################################################################ | ||
8 | |||
9 | export type MOAuthToken = Omit<OAuthTokenModel, 'User' | 'OAuthClients'> | ||
10 | |||
11 | export type MOAuthTokenUser = MOAuthToken & | ||
12 | Use<'User', MUserAccountUrl> & | ||
13 | { user?: MUserAccountUrl } | ||
diff --git a/server/typings/models/server/index.d.ts b/server/typings/models/server/index.d.ts new file mode 100644 index 000000000..c853795ad --- /dev/null +++ b/server/typings/models/server/index.d.ts | |||
@@ -0,0 +1,3 @@ | |||
1 | export * from './plugin' | ||
2 | export * from './server' | ||
3 | export * from './server-blocklist' | ||
diff --git a/server/typings/models/server/plugin.ts b/server/typings/models/server/plugin.ts new file mode 100644 index 000000000..94674c318 --- /dev/null +++ b/server/typings/models/server/plugin.ts | |||
@@ -0,0 +1,10 @@ | |||
1 | import { PluginModel } from '@server/models/server/plugin' | ||
2 | |||
3 | export type MPlugin = PluginModel | ||
4 | |||
5 | // ############################################################################ | ||
6 | |||
7 | // Format for API or AP object | ||
8 | |||
9 | export type MPluginFormattable = Pick<MPlugin, 'name' | 'type' | 'version' | 'latestVersion' | 'enabled' | 'uninstalled' | ||
10 | | 'peertubeEngine' | 'description' | 'homepage' | 'settings' | 'createdAt' | 'updatedAt'> | ||
diff --git a/server/typings/models/server/server-blocklist.ts b/server/typings/models/server/server-blocklist.ts new file mode 100644 index 000000000..c81f604f5 --- /dev/null +++ b/server/typings/models/server/server-blocklist.ts | |||
@@ -0,0 +1,23 @@ | |||
1 | import { ServerBlocklistModel } from '@server/models/server/server-blocklist' | ||
2 | import { PickWith } from '@server/typings/utils' | ||
3 | import { MAccountDefault, MAccountFormattable, MServer, MServerFormattable } from '@server/typings/models' | ||
4 | |||
5 | type Use<K extends keyof ServerBlocklistModel, M> = PickWith<ServerBlocklistModel, K, M> | ||
6 | |||
7 | // ############################################################################ | ||
8 | |||
9 | export type MServerBlocklist = Omit<ServerBlocklistModel, 'ByAccount' | 'BlockedServer'> | ||
10 | |||
11 | // ############################################################################ | ||
12 | |||
13 | export type MServerBlocklistAccountServer = MServerBlocklist & | ||
14 | Use<'ByAccount', MAccountDefault> & | ||
15 | Use<'BlockedServer', MServer> | ||
16 | |||
17 | // ############################################################################ | ||
18 | |||
19 | // Format for API or AP object | ||
20 | |||
21 | export type MServerBlocklistFormattable = Pick<MServerBlocklist, 'createdAt'> & | ||
22 | Use<'ByAccount', MAccountFormattable> & | ||
23 | Use<'BlockedServer', MServerFormattable> | ||
diff --git a/server/typings/models/server/server.ts b/server/typings/models/server/server.ts new file mode 100644 index 000000000..190cc0c28 --- /dev/null +++ b/server/typings/models/server/server.ts | |||
@@ -0,0 +1,24 @@ | |||
1 | import { ServerModel } from '../../../models/server/server' | ||
2 | import { FunctionProperties, PickWith } from '../../utils' | ||
3 | import { MAccountBlocklistId } from '../account' | ||
4 | |||
5 | type Use<K extends keyof ServerModel, M> = PickWith<ServerModel, K, M> | ||
6 | |||
7 | // ############################################################################ | ||
8 | |||
9 | export type MServer = Omit<ServerModel, 'Actors' | 'BlockedByAccounts'> | ||
10 | |||
11 | // ############################################################################ | ||
12 | |||
13 | export type MServerHost = Pick<MServer, 'host'> | ||
14 | export type MServerRedundancyAllowed = Pick<MServer, 'redundancyAllowed'> | ||
15 | |||
16 | export type MServerHostBlocks = MServerHost & | ||
17 | Use<'BlockedByAccounts', MAccountBlocklistId[]> | ||
18 | |||
19 | // ############################################################################ | ||
20 | |||
21 | // Format for API or AP object | ||
22 | |||
23 | export type MServerFormattable = FunctionProperties<MServer> & | ||
24 | Pick<MServer, 'host'> | ||
diff --git a/server/typings/models/user/index.d.ts b/server/typings/models/user/index.d.ts new file mode 100644 index 000000000..6657b2128 --- /dev/null +++ b/server/typings/models/user/index.d.ts | |||
@@ -0,0 +1,4 @@ | |||
1 | export * from './user' | ||
2 | export * from './user-notification' | ||
3 | export * from './user-notification-setting' | ||
4 | export * from './user-video-history' | ||
diff --git a/server/typings/models/user/user-notification-setting.ts b/server/typings/models/user/user-notification-setting.ts new file mode 100644 index 000000000..c674add1b --- /dev/null +++ b/server/typings/models/user/user-notification-setting.ts | |||
@@ -0,0 +1,9 @@ | |||
1 | import { UserNotificationSettingModel } from '@server/models/account/user-notification-setting' | ||
2 | |||
3 | export type MNotificationSetting = Omit<UserNotificationSettingModel, 'User'> | ||
4 | |||
5 | // ############################################################################ | ||
6 | |||
7 | // Format for API or AP object | ||
8 | |||
9 | export type MNotificationSettingFormattable = MNotificationSetting | ||
diff --git a/server/typings/models/user/user-notification.ts b/server/typings/models/user/user-notification.ts new file mode 100644 index 000000000..1cdc691b0 --- /dev/null +++ b/server/typings/models/user/user-notification.ts | |||
@@ -0,0 +1,78 @@ | |||
1 | import { UserNotificationModel } from '../../../models/account/user-notification' | ||
2 | import { PickWith, PickWithOpt } from '../../utils' | ||
3 | import { VideoModel } from '../../../models/video/video' | ||
4 | import { ActorModel } from '../../../models/activitypub/actor' | ||
5 | import { ServerModel } from '../../../models/server/server' | ||
6 | import { AvatarModel } from '../../../models/avatar/avatar' | ||
7 | import { VideoChannelModel } from '../../../models/video/video-channel' | ||
8 | import { AccountModel } from '../../../models/account/account' | ||
9 | import { VideoCommentModel } from '../../../models/video/video-comment' | ||
10 | import { VideoAbuseModel } from '../../../models/video/video-abuse' | ||
11 | import { VideoBlacklistModel } from '../../../models/video/video-blacklist' | ||
12 | import { VideoImportModel } from '../../../models/video/video-import' | ||
13 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' | ||
14 | |||
15 | type Use<K extends keyof UserNotificationModel, M> = PickWith<UserNotificationModel, K, M> | ||
16 | |||
17 | // ############################################################################ | ||
18 | |||
19 | export namespace UserNotificationIncludes { | ||
20 | export type VideoInclude = Pick<VideoModel, 'id' | 'uuid' | 'name'> | ||
21 | export type VideoIncludeChannel = VideoInclude & | ||
22 | PickWith<VideoModel, 'VideoChannel', VideoChannelIncludeActor> | ||
23 | |||
24 | export type ActorInclude = Pick<ActorModel, 'preferredUsername' | 'getHost'> & | ||
25 | PickWith<ActorModel, 'Avatar', Pick<AvatarModel, 'filename' | 'getStaticPath'>> & | ||
26 | PickWith<ActorModel, 'Server', Pick<ServerModel, 'host'>> | ||
27 | |||
28 | export type VideoChannelInclude = Pick<VideoChannelModel, 'id' | 'name' | 'getDisplayName'> | ||
29 | export type VideoChannelIncludeActor = VideoChannelInclude & | ||
30 | PickWith<VideoChannelModel, 'Actor', ActorInclude> | ||
31 | |||
32 | export type AccountInclude = Pick<AccountModel, 'id' | 'name' | 'getDisplayName'> | ||
33 | export type AccountIncludeActor = AccountInclude & | ||
34 | PickWith<AccountModel, 'Actor', ActorInclude> | ||
35 | |||
36 | export type VideoCommentInclude = Pick<VideoCommentModel, 'id' | 'originCommentId' | 'getThreadId'> & | ||
37 | PickWith<VideoCommentModel, 'Account', AccountIncludeActor> & | ||
38 | PickWith<VideoCommentModel, 'Video', VideoInclude> | ||
39 | |||
40 | export type VideoAbuseInclude = Pick<VideoAbuseModel, 'id'> & | ||
41 | PickWith<VideoAbuseModel, 'Video', VideoInclude> | ||
42 | |||
43 | export type VideoBlacklistInclude = Pick<VideoBlacklistModel, 'id'> & | ||
44 | PickWith<VideoAbuseModel, 'Video', VideoInclude> | ||
45 | |||
46 | export type VideoImportInclude = Pick<VideoImportModel, 'id' | 'magnetUri' | 'targetUrl' | 'torrentName'> & | ||
47 | PickWith<VideoImportModel, 'Video', VideoInclude> | ||
48 | |||
49 | export type ActorFollower = Pick<ActorModel, 'preferredUsername' | 'getHost'> & | ||
50 | PickWith<ActorModel, 'Account', AccountInclude> & | ||
51 | PickWith<ActorModel, 'Server', Pick<ServerModel, 'host'>> & | ||
52 | PickWithOpt<ActorModel, 'Avatar', Pick<AvatarModel, 'filename' | 'getStaticPath'>> | ||
53 | |||
54 | export type ActorFollowing = Pick<ActorModel, 'preferredUsername' | 'type' | 'getHost'> & | ||
55 | PickWith<ActorModel, 'VideoChannel', VideoChannelInclude> & | ||
56 | PickWith<ActorModel, 'Account', AccountInclude> & | ||
57 | PickWith<ActorModel, 'Server', Pick<ServerModel, 'host'>> | ||
58 | |||
59 | export type ActorFollowInclude = Pick<ActorFollowModel, 'id' | 'state'> & | ||
60 | PickWith<ActorFollowModel, 'ActorFollower', ActorFollower> & | ||
61 | PickWith<ActorFollowModel, 'ActorFollowing', ActorFollowing> | ||
62 | } | ||
63 | |||
64 | // ############################################################################ | ||
65 | |||
66 | export type MUserNotification = Omit<UserNotificationModel, 'User' | 'Video' | 'Comment' | 'VideoAbuse' | 'VideoBlacklist' | | ||
67 | 'VideoImport' | 'Account' | 'ActorFollow'> | ||
68 | |||
69 | // ############################################################################ | ||
70 | |||
71 | export type UserNotificationModelForApi = MUserNotification & | ||
72 | Use<'Video', UserNotificationIncludes.VideoIncludeChannel> & | ||
73 | Use<'Comment', UserNotificationIncludes.VideoCommentInclude> & | ||
74 | Use<'VideoAbuse', UserNotificationIncludes.VideoAbuseInclude> & | ||
75 | Use<'VideoBlacklist', UserNotificationIncludes.VideoBlacklistInclude> & | ||
76 | Use<'VideoImport', UserNotificationIncludes.VideoImportInclude> & | ||
77 | Use<'ActorFollow', UserNotificationIncludes.ActorFollowInclude> & | ||
78 | Use<'Account', UserNotificationIncludes.AccountIncludeActor> | ||
diff --git a/server/typings/models/user/user-video-history.ts b/server/typings/models/user/user-video-history.ts new file mode 100644 index 000000000..62673ab1b --- /dev/null +++ b/server/typings/models/user/user-video-history.ts | |||
@@ -0,0 +1,5 @@ | |||
1 | import { UserVideoHistoryModel } from '../../../models/account/user-video-history' | ||
2 | |||
3 | export type MUserVideoHistory = Omit<UserVideoHistoryModel, 'Video' | 'User'> | ||
4 | |||
5 | export type MUserVideoHistoryTime = Pick<MUserVideoHistory, 'currentTime'> | ||
diff --git a/server/typings/models/user/user.ts b/server/typings/models/user/user.ts new file mode 100644 index 000000000..52d6d4a05 --- /dev/null +++ b/server/typings/models/user/user.ts | |||
@@ -0,0 +1,70 @@ | |||
1 | import { UserModel } from '../../../models/account/user' | ||
2 | import { PickWith, PickWithOpt } from '../../utils' | ||
3 | import { | ||
4 | MAccount, | ||
5 | MAccountDefault, | ||
6 | MAccountDefaultChannelDefault, | ||
7 | MAccountFormattable, | ||
8 | MAccountId, | ||
9 | MAccountIdActorId, | ||
10 | MAccountUrl | ||
11 | } from '../account' | ||
12 | import { MNotificationSetting, MNotificationSettingFormattable } from './user-notification-setting' | ||
13 | import { AccountModel } from '@server/models/account/account' | ||
14 | import { MChannelFormattable } from '@server/typings/models' | ||
15 | |||
16 | type Use<K extends keyof UserModel, M> = PickWith<UserModel, K, M> | ||
17 | |||
18 | // ############################################################################ | ||
19 | |||
20 | export type MUser = Omit<UserModel, 'Account' | 'NotificationSetting' | 'VideoImports' | 'OAuthTokens'> | ||
21 | |||
22 | // ############################################################################ | ||
23 | |||
24 | export type MUserQuotaUsed = MUser & { videoQuotaUsed?: number, videoQuotaUsedDaily?: number } | ||
25 | export type MUserId = Pick<UserModel, 'id'> | ||
26 | |||
27 | // ############################################################################ | ||
28 | |||
29 | // With account | ||
30 | |||
31 | export type MUserAccountId = MUser & | ||
32 | Use<'Account', MAccountId> | ||
33 | |||
34 | export type MUserAccountUrl = MUser & | ||
35 | Use<'Account', MAccountUrl & MAccountIdActorId> | ||
36 | |||
37 | export type MUserAccount = MUser & | ||
38 | Use<'Account', MAccount> | ||
39 | |||
40 | export type MUserAccountDefault = MUser & | ||
41 | Use<'Account', MAccountDefault> | ||
42 | |||
43 | // With channel | ||
44 | |||
45 | export type MUserNotifSettingChannelDefault = MUser & | ||
46 | Use<'NotificationSetting', MNotificationSetting> & | ||
47 | Use<'Account', MAccountDefaultChannelDefault> | ||
48 | |||
49 | // With notification settings | ||
50 | |||
51 | export type MUserWithNotificationSetting = MUser & | ||
52 | Use<'NotificationSetting', MNotificationSetting> | ||
53 | |||
54 | export type MUserNotifSettingAccount = MUser & | ||
55 | Use<'NotificationSetting', MNotificationSetting> & | ||
56 | Use<'Account', MAccount> | ||
57 | |||
58 | // Default scope | ||
59 | |||
60 | export type MUserDefault = MUser & | ||
61 | Use<'NotificationSetting', MNotificationSetting> & | ||
62 | Use<'Account', MAccountDefault> | ||
63 | |||
64 | // ############################################################################ | ||
65 | |||
66 | // Format for API or AP object | ||
67 | |||
68 | export type MUserFormattable = MUserQuotaUsed & | ||
69 | Use<'Account', MAccountFormattable & PickWithOpt<AccountModel, 'VideoChannels', MChannelFormattable[]>> & | ||
70 | PickWithOpt<UserModel, 'NotificationSetting', MNotificationSettingFormattable> | ||
diff --git a/server/typings/models/video-share.ts b/server/typings/models/video-share.ts deleted file mode 100644 index 1406749d2..000000000 --- a/server/typings/models/video-share.ts +++ /dev/null | |||
@@ -1,3 +0,0 @@ | |||
1 | import { VideoShareModel } from '../../models/video/video-share' | ||
2 | |||
3 | export type VideoShareModelOnly = Omit<VideoShareModel, 'Actor' | 'Video'> | ||
diff --git a/server/typings/models/video/index.d.ts b/server/typings/models/video/index.d.ts new file mode 100644 index 000000000..bd69c8a4b --- /dev/null +++ b/server/typings/models/video/index.d.ts | |||
@@ -0,0 +1,18 @@ | |||
1 | export * from './schedule-video-update' | ||
2 | export * from './tag' | ||
3 | export * from './thumbnail' | ||
4 | export * from './video' | ||
5 | export * from './video-abuse' | ||
6 | export * from './video-blacklist' | ||
7 | export * from './video-caption' | ||
8 | export * from './video-change-ownership' | ||
9 | export * from './video-channels' | ||
10 | export * from './video-comment' | ||
11 | export * from './video-file' | ||
12 | export * from './video-import' | ||
13 | export * from './video-playlist' | ||
14 | export * from './video-playlist-element' | ||
15 | export * from './video-rate' | ||
16 | export * from './video-redundancy' | ||
17 | export * from './video-share' | ||
18 | export * from './video-streaming-playlist' | ||
diff --git a/server/typings/models/video/schedule-video-update.ts b/server/typings/models/video/schedule-video-update.ts new file mode 100644 index 000000000..ada9af06e --- /dev/null +++ b/server/typings/models/video/schedule-video-update.ts | |||
@@ -0,0 +1,9 @@ | |||
1 | import { ScheduleVideoUpdateModel } from '../../../models/video/schedule-video-update' | ||
2 | |||
3 | export type MScheduleVideoUpdate = Omit<ScheduleVideoUpdateModel, 'Video'> | ||
4 | |||
5 | // ############################################################################ | ||
6 | |||
7 | // Format for API or AP object | ||
8 | |||
9 | export type MScheduleVideoUpdateFormattable = Pick<MScheduleVideoUpdate, 'updateAt' | 'privacy'> | ||
diff --git a/server/typings/models/video/tag.ts b/server/typings/models/video/tag.ts new file mode 100644 index 000000000..64a68873e --- /dev/null +++ b/server/typings/models/video/tag.ts | |||
@@ -0,0 +1,3 @@ | |||
1 | import { TagModel } from '../../../models/video/tag' | ||
2 | |||
3 | export type MTag = Omit<TagModel, 'Videos'> | ||
diff --git a/server/typings/models/video/thumbnail.ts b/server/typings/models/video/thumbnail.ts new file mode 100644 index 000000000..c03ba55ac --- /dev/null +++ b/server/typings/models/video/thumbnail.ts | |||
@@ -0,0 +1,3 @@ | |||
1 | import { ThumbnailModel } from '../../../models/video/thumbnail' | ||
2 | |||
3 | export type MThumbnail = Omit<ThumbnailModel, 'Video' | 'VideoPlaylist'> | ||
diff --git a/server/typings/models/video/video-abuse.ts b/server/typings/models/video/video-abuse.ts new file mode 100644 index 000000000..e38c3f586 --- /dev/null +++ b/server/typings/models/video/video-abuse.ts | |||
@@ -0,0 +1,31 @@ | |||
1 | import { VideoAbuseModel } from '../../../models/video/video-abuse' | ||
2 | import { PickWith } from '../../utils' | ||
3 | import { MVideo } from './video' | ||
4 | import { MAccountDefault, MAccountFormattable } from '../account' | ||
5 | |||
6 | type Use<K extends keyof VideoAbuseModel, M> = PickWith<VideoAbuseModel, K, M> | ||
7 | |||
8 | // ############################################################################ | ||
9 | |||
10 | export type MVideoAbuse = Omit<VideoAbuseModel, 'Account' | 'Video' | 'toActivityPubObject'> | ||
11 | |||
12 | // ############################################################################ | ||
13 | |||
14 | export type MVideoAbuseId = Pick<VideoAbuseModel, 'id'> | ||
15 | |||
16 | export type MVideoAbuseVideo = MVideoAbuse & | ||
17 | Pick<VideoAbuseModel, 'toActivityPubObject'> & | ||
18 | Use<'Video', MVideo> | ||
19 | |||
20 | export type MVideoAbuseAccountVideo = MVideoAbuse & | ||
21 | Pick<VideoAbuseModel, 'toActivityPubObject'> & | ||
22 | Use<'Video', MVideo> & | ||
23 | Use<'Account', MAccountDefault> | ||
24 | |||
25 | // ############################################################################ | ||
26 | |||
27 | // Format for API or AP object | ||
28 | |||
29 | export type MVideoAbuseFormattable = MVideoAbuse & | ||
30 | Use<'Account', MAccountFormattable> & | ||
31 | Use<'Video', Pick<MVideo, 'id' | 'uuid' | 'name'>> | ||
diff --git a/server/typings/models/video/video-blacklist.ts b/server/typings/models/video/video-blacklist.ts new file mode 100644 index 000000000..e12880454 --- /dev/null +++ b/server/typings/models/video/video-blacklist.ts | |||
@@ -0,0 +1,27 @@ | |||
1 | import { VideoBlacklistModel } from '../../../models/video/video-blacklist' | ||
2 | import { PickWith } from '@server/typings/utils' | ||
3 | import { MVideo, MVideoFormattable } from '@server/typings/models' | ||
4 | |||
5 | type Use<K extends keyof VideoBlacklistModel, M> = PickWith<VideoBlacklistModel, K, M> | ||
6 | |||
7 | // ############################################################################ | ||
8 | |||
9 | export type MVideoBlacklist = Omit<VideoBlacklistModel, 'Video'> | ||
10 | |||
11 | export type MVideoBlacklistLight = Pick<MVideoBlacklist, 'id' | 'reason' | 'unfederated'> | ||
12 | export type MVideoBlacklistUnfederated = Pick<MVideoBlacklist, 'unfederated'> | ||
13 | |||
14 | // ############################################################################ | ||
15 | |||
16 | export type MVideoBlacklistLightVideo = MVideoBlacklistLight & | ||
17 | Use<'Video', MVideo> | ||
18 | |||
19 | export type MVideoBlacklistVideo = MVideoBlacklist & | ||
20 | Use<'Video', MVideo> | ||
21 | |||
22 | // ############################################################################ | ||
23 | |||
24 | // Format for API or AP object | ||
25 | |||
26 | export type MVideoBlacklistFormattable = MVideoBlacklist & | ||
27 | Use<'Video', MVideoFormattable> | ||
diff --git a/server/typings/models/video/video-caption.ts b/server/typings/models/video/video-caption.ts new file mode 100644 index 000000000..7cb2a2ad3 --- /dev/null +++ b/server/typings/models/video/video-caption.ts | |||
@@ -0,0 +1,24 @@ | |||
1 | import { VideoCaptionModel } from '../../../models/video/video-caption' | ||
2 | import { FunctionProperties, PickWith } from '@server/typings/utils' | ||
3 | import { MVideo, MVideoUUID } from '@server/typings/models' | ||
4 | |||
5 | type Use<K extends keyof VideoCaptionModel, M> = PickWith<VideoCaptionModel, K, M> | ||
6 | |||
7 | // ############################################################################ | ||
8 | |||
9 | export type MVideoCaption = Omit<VideoCaptionModel, 'Video'> | ||
10 | |||
11 | // ############################################################################ | ||
12 | |||
13 | export type MVideoCaptionLanguage = Pick<MVideoCaption, 'language'> | ||
14 | |||
15 | export type MVideoCaptionVideo = MVideoCaption & | ||
16 | Use<'Video', Pick<MVideo, 'id' | 'remote' | 'uuid'>> | ||
17 | |||
18 | // ############################################################################ | ||
19 | |||
20 | // Format for API or AP object | ||
21 | |||
22 | export type MVideoCaptionFormattable = FunctionProperties<MVideoCaption> & | ||
23 | Pick<MVideoCaption, 'language'> & | ||
24 | Use<'Video', MVideoUUID> | ||
diff --git a/server/typings/models/video/video-change-ownership.ts b/server/typings/models/video/video-change-ownership.ts new file mode 100644 index 000000000..72634cdb2 --- /dev/null +++ b/server/typings/models/video/video-change-ownership.ts | |||
@@ -0,0 +1,23 @@ | |||
1 | import { VideoChangeOwnershipModel } from '@server/models/video/video-change-ownership' | ||
2 | import { PickWith } from '@server/typings/utils' | ||
3 | import { MAccountDefault, MAccountFormattable, MVideo, MVideoWithFileThumbnail } from '@server/typings/models' | ||
4 | |||
5 | type Use<K extends keyof VideoChangeOwnershipModel, M> = PickWith<VideoChangeOwnershipModel, K, M> | ||
6 | |||
7 | // ############################################################################ | ||
8 | |||
9 | export type MVideoChangeOwnership = Omit<VideoChangeOwnershipModel, 'Initiator' | 'NextOwner' | 'Video'> | ||
10 | |||
11 | export type MVideoChangeOwnershipFull = MVideoChangeOwnership & | ||
12 | Use<'Initiator', MAccountDefault> & | ||
13 | Use<'NextOwner', MAccountDefault> & | ||
14 | Use<'Video', MVideoWithFileThumbnail> | ||
15 | |||
16 | // ############################################################################ | ||
17 | |||
18 | // Format for API or AP object | ||
19 | |||
20 | export type MVideoChangeOwnershipFormattable = Pick<MVideoChangeOwnership, 'id' | 'status' | 'createdAt'> & | ||
21 | Use<'Initiator', MAccountFormattable> & | ||
22 | Use<'NextOwner', MAccountFormattable> & | ||
23 | Use<'Video', Pick<MVideo, 'id' | 'uuid' | 'url' | 'name'>> | ||
diff --git a/server/typings/models/video/video-channels.ts b/server/typings/models/video/video-channels.ts new file mode 100644 index 000000000..292d0ac95 --- /dev/null +++ b/server/typings/models/video/video-channels.ts | |||
@@ -0,0 +1,126 @@ | |||
1 | import { FunctionProperties, PickWith, PickWithOpt } from '../../utils' | ||
2 | import { VideoChannelModel } from '../../../models/video/video-channel' | ||
3 | import { | ||
4 | MAccountActor, | ||
5 | MAccountAPI, | ||
6 | MAccountDefault, | ||
7 | MAccountFormattable, | ||
8 | MAccountLight, | ||
9 | MAccountSummaryBlocks, | ||
10 | MAccountSummaryFormattable, | ||
11 | MAccountUrl, | ||
12 | MAccountUserId, | ||
13 | MActor, | ||
14 | MActorAccountChannelId, | ||
15 | MActorAP, | ||
16 | MActorAPI, | ||
17 | MActorDefault, | ||
18 | MActorDefaultLight, | ||
19 | MActorFormattable, | ||
20 | MActorLight, | ||
21 | MActorSummary, | ||
22 | MActorSummaryFormattable, MActorUrl | ||
23 | } from '../account' | ||
24 | import { MVideo } from './video' | ||
25 | |||
26 | type Use<K extends keyof VideoChannelModel, M> = PickWith<VideoChannelModel, K, M> | ||
27 | |||
28 | // ############################################################################ | ||
29 | |||
30 | export type MChannel = Omit<VideoChannelModel, 'Actor' | 'Account' | 'Videos' | 'VideoPlaylists'> | ||
31 | |||
32 | // ############################################################################ | ||
33 | |||
34 | export type MChannelId = Pick<MChannel, 'id'> | ||
35 | |||
36 | // ############################################################################ | ||
37 | |||
38 | export type MChannelIdActor = MChannelId & | ||
39 | Use<'Actor', MActorAccountChannelId> | ||
40 | |||
41 | export type MChannelUserId = Pick<MChannel, 'accountId'> & | ||
42 | Use<'Account', MAccountUserId> | ||
43 | |||
44 | export type MChannelActor = MChannel & | ||
45 | Use<'Actor', MActor> | ||
46 | |||
47 | export type MChannelUrl = Use<'Actor', MActorUrl> | ||
48 | |||
49 | // Default scope | ||
50 | export type MChannelDefault = MChannel & | ||
51 | Use<'Actor', MActorDefault> | ||
52 | |||
53 | // ############################################################################ | ||
54 | |||
55 | // Not all association attributes | ||
56 | |||
57 | export type MChannelLight = MChannel & | ||
58 | Use<'Actor', MActorDefaultLight> | ||
59 | |||
60 | export type MChannelActorLight = MChannel & | ||
61 | Use<'Actor', MActorLight> | ||
62 | |||
63 | export type MChannelAccountLight = MChannel & | ||
64 | Use<'Actor', MActorDefaultLight> & | ||
65 | Use<'Account', MAccountLight> | ||
66 | |||
67 | // ############################################################################ | ||
68 | |||
69 | // Account associations | ||
70 | |||
71 | export type MChannelAccountActor = MChannel & | ||
72 | Use<'Account', MAccountActor> | ||
73 | |||
74 | export type MChannelAccountDefault = MChannel & | ||
75 | Use<'Actor', MActorDefault> & | ||
76 | Use<'Account', MAccountDefault> | ||
77 | |||
78 | export type MChannelActorAccountActor = MChannel & | ||
79 | Use<'Account', MAccountActor> & | ||
80 | Use<'Actor', MActor> | ||
81 | |||
82 | // ############################################################################ | ||
83 | |||
84 | // Videos associations | ||
85 | export type MChannelVideos = MChannel & | ||
86 | Use<'Videos', MVideo[]> | ||
87 | |||
88 | export type MChannelActorAccountDefaultVideos = MChannel & | ||
89 | Use<'Actor', MActorDefault> & | ||
90 | Use<'Account', MAccountDefault> & | ||
91 | Use<'Videos', MVideo[]> | ||
92 | |||
93 | // ############################################################################ | ||
94 | |||
95 | // For API | ||
96 | |||
97 | export type MChannelSummary = FunctionProperties<MChannel> & | ||
98 | Pick<MChannel, 'id' | 'name' | 'description' | 'actorId'> & | ||
99 | Use<'Actor', MActorSummary> | ||
100 | |||
101 | export type MChannelSummaryAccount = MChannelSummary & | ||
102 | Use<'Account', MAccountSummaryBlocks> | ||
103 | |||
104 | export type MChannelAPI = MChannel & | ||
105 | Use<'Actor', MActorAPI> & | ||
106 | Use<'Account', MAccountAPI> | ||
107 | |||
108 | // ############################################################################ | ||
109 | |||
110 | // Format for API or AP object | ||
111 | |||
112 | export type MChannelSummaryFormattable = FunctionProperties<MChannel> & | ||
113 | Pick<MChannel, 'id' | 'name'> & | ||
114 | Use<'Actor', MActorSummaryFormattable> | ||
115 | |||
116 | export type MChannelAccountSummaryFormattable = MChannelSummaryFormattable & | ||
117 | Use<'Account', MAccountSummaryFormattable> | ||
118 | |||
119 | export type MChannelFormattable = FunctionProperties<MChannel> & | ||
120 | Pick<MChannel, 'id' | 'name' | 'description' | 'createdAt' | 'updatedAt' | 'support'> & | ||
121 | Use<'Actor', MActorFormattable> & | ||
122 | PickWithOpt<VideoChannelModel, 'Account', MAccountFormattable> | ||
123 | |||
124 | export type MChannelAP = Pick<MChannel, 'name' | 'description' | 'support'> & | ||
125 | Use<'Actor', MActorAP> & | ||
126 | Use<'Account', MAccountUrl> | ||
diff --git a/server/typings/models/video/video-comment.ts b/server/typings/models/video/video-comment.ts new file mode 100644 index 000000000..4fd1c29e8 --- /dev/null +++ b/server/typings/models/video/video-comment.ts | |||
@@ -0,0 +1,57 @@ | |||
1 | import { VideoCommentModel } from '../../../models/video/video-comment' | ||
2 | import { PickWith, PickWithOpt } from '../../utils' | ||
3 | import { MAccountDefault, MAccountFormattable, MAccountUrl, MActorUrl } from '../account' | ||
4 | import { MVideoAccountLight, MVideoFeed, MVideoIdUrl, MVideoUrl } from './video' | ||
5 | |||
6 | type Use<K extends keyof VideoCommentModel, M> = PickWith<VideoCommentModel, K, M> | ||
7 | |||
8 | // ############################################################################ | ||
9 | |||
10 | export type MComment = Omit<VideoCommentModel, 'OriginVideoComment' | 'InReplyToVideoComment' | 'Video' | 'Account'> | ||
11 | export type MCommentTotalReplies = MComment & { totalReplies?: number } | ||
12 | export type MCommentId = Pick<MComment, 'id'> | ||
13 | export type MCommentUrl = Pick<MComment, 'url'> | ||
14 | |||
15 | // ############################################################################ | ||
16 | |||
17 | export type MCommentOwner = MComment & | ||
18 | Use<'Account', MAccountDefault> | ||
19 | |||
20 | export type MCommentVideo = MComment & | ||
21 | Use<'Video', MVideoAccountLight> | ||
22 | |||
23 | export type MCommentReply = MComment & | ||
24 | Use<'InReplyToVideoComment', MComment> | ||
25 | |||
26 | export type MCommentOwnerVideo = MComment & | ||
27 | Use<'Account', MAccountDefault> & | ||
28 | Use<'Video', MVideoAccountLight> | ||
29 | |||
30 | export type MCommentOwnerVideoReply = MComment & | ||
31 | Use<'Account', MAccountDefault> & | ||
32 | Use<'Video', MVideoAccountLight> & | ||
33 | Use<'InReplyToVideoComment', MComment> | ||
34 | |||
35 | export type MCommentOwnerReplyVideoLight = MComment & | ||
36 | Use<'Account', MAccountDefault> & | ||
37 | Use<'InReplyToVideoComment', MComment> & | ||
38 | Use<'Video', MVideoIdUrl> | ||
39 | |||
40 | export type MCommentOwnerVideoFeed = MCommentOwner & | ||
41 | Use<'Video', MVideoFeed> | ||
42 | |||
43 | // ############################################################################ | ||
44 | |||
45 | export type MCommentAPI = MComment & { totalReplies: number } | ||
46 | |||
47 | // ############################################################################ | ||
48 | |||
49 | // Format for API or AP object | ||
50 | |||
51 | export type MCommentFormattable = MCommentTotalReplies & | ||
52 | Use<'Account', MAccountFormattable> | ||
53 | |||
54 | export type MCommentAP = MComment & | ||
55 | Use<'Account', MAccountUrl> & | ||
56 | PickWithOpt<VideoCommentModel, 'Video', MVideoUrl> & | ||
57 | PickWithOpt<VideoCommentModel, 'InReplyToVideoComment', MCommentUrl> | ||
diff --git a/server/typings/models/video/video-file.ts b/server/typings/models/video/video-file.ts new file mode 100644 index 000000000..484351a8d --- /dev/null +++ b/server/typings/models/video/video-file.ts | |||
@@ -0,0 +1,19 @@ | |||
1 | import { VideoFileModel } from '../../../models/video/video-file' | ||
2 | import { PickWith, PickWithOpt } from '../../utils' | ||
3 | import { MVideo, MVideoUUID } from './video' | ||
4 | import { MVideoRedundancyFileUrl } from './video-redundancy' | ||
5 | |||
6 | type Use<K extends keyof VideoFileModel, M> = PickWith<VideoFileModel, K, M> | ||
7 | |||
8 | // ############################################################################ | ||
9 | |||
10 | export type MVideoFile = Omit<VideoFileModel, 'Video' | 'RedundancyVideos'> | ||
11 | |||
12 | export type MVideoFileVideo = MVideoFile & | ||
13 | Use<'Video', MVideo> | ||
14 | |||
15 | export type MVideoFileVideoUUID = MVideoFile & | ||
16 | Use<'Video', MVideoUUID> | ||
17 | |||
18 | export type MVideoFileRedundanciesOpt = MVideoFile & | ||
19 | PickWithOpt<VideoFileModel, 'RedundancyVideos', MVideoRedundancyFileUrl[]> | ||
diff --git a/server/typings/models/video/video-import.ts b/server/typings/models/video/video-import.ts new file mode 100644 index 000000000..c6a1c5b66 --- /dev/null +++ b/server/typings/models/video/video-import.ts | |||
@@ -0,0 +1,31 @@ | |||
1 | import { VideoImportModel } from '@server/models/video/video-import' | ||
2 | import { PickWith, PickWithOpt } from '@server/typings/utils' | ||
3 | import { MUser, MVideo, MVideoAccountLight, MVideoFormattable, MVideoTag, MVideoThumbnail, MVideoWithFile } from '@server/typings/models' | ||
4 | |||
5 | type Use<K extends keyof VideoImportModel, M> = PickWith<VideoImportModel, K, M> | ||
6 | |||
7 | // ############################################################################ | ||
8 | |||
9 | export type MVideoImport = Omit<VideoImportModel, 'User' | 'Video'> | ||
10 | |||
11 | export type MVideoImportVideo = MVideoImport & | ||
12 | Use<'Video', MVideo> | ||
13 | |||
14 | // ############################################################################ | ||
15 | |||
16 | type VideoAssociation = MVideoTag & MVideoAccountLight & MVideoThumbnail | ||
17 | |||
18 | export type MVideoImportDefault = MVideoImport & | ||
19 | Use<'User', MUser> & | ||
20 | Use<'Video', VideoAssociation> | ||
21 | |||
22 | export type MVideoImportDefaultFiles = MVideoImport & | ||
23 | Use<'User', MUser> & | ||
24 | Use<'Video', VideoAssociation & MVideoWithFile> | ||
25 | |||
26 | // ############################################################################ | ||
27 | |||
28 | // Format for API or AP object | ||
29 | |||
30 | export type MVideoImportFormattable = MVideoImport & | ||
31 | PickWithOpt<VideoImportModel, 'Video', MVideoFormattable & MVideoTag> | ||
diff --git a/server/typings/models/video/video-playlist-element.ts b/server/typings/models/video/video-playlist-element.ts new file mode 100644 index 000000000..7b1b993ce --- /dev/null +++ b/server/typings/models/video/video-playlist-element.ts | |||
@@ -0,0 +1,34 @@ | |||
1 | import { VideoPlaylistElementModel } from '@server/models/video/video-playlist-element' | ||
2 | import { PickWith } from '@server/typings/utils' | ||
3 | import { MVideoFormattable, MVideoPlaylistPrivacy, MVideoThumbnail, MVideoUrl } from '@server/typings/models' | ||
4 | |||
5 | type Use<K extends keyof VideoPlaylistElementModel, M> = PickWith<VideoPlaylistElementModel, K, M> | ||
6 | |||
7 | // ############################################################################ | ||
8 | |||
9 | export type MVideoPlaylistElement = Omit<VideoPlaylistElementModel, 'VideoPlaylist' | 'Video'> | ||
10 | |||
11 | // ############################################################################ | ||
12 | |||
13 | export type MVideoPlaylistElementId = Pick<MVideoPlaylistElement, 'id'> | ||
14 | |||
15 | export type MVideoPlaylistElementLight = Pick<MVideoPlaylistElement, 'id' | 'videoId' | 'startTimestamp' | 'stopTimestamp'> | ||
16 | |||
17 | // ############################################################################ | ||
18 | |||
19 | export type MVideoPlaylistVideoThumbnail = MVideoPlaylistElement & | ||
20 | Use<'Video', MVideoThumbnail> | ||
21 | |||
22 | export type MVideoPlaylistElementVideoUrlPlaylistPrivacy = MVideoPlaylistElement & | ||
23 | Use<'Video', MVideoUrl> & | ||
24 | Use<'VideoPlaylist', MVideoPlaylistPrivacy> | ||
25 | |||
26 | // ############################################################################ | ||
27 | |||
28 | // Format for API or AP object | ||
29 | |||
30 | export type MVideoPlaylistElementFormattable = MVideoPlaylistElement & | ||
31 | Use<'Video', MVideoFormattable> | ||
32 | |||
33 | export type MVideoPlaylistElementAP = MVideoPlaylistElement & | ||
34 | Use<'Video', MVideoUrl> | ||
diff --git a/server/typings/models/video/video-playlist.ts b/server/typings/models/video/video-playlist.ts new file mode 100644 index 000000000..a40c7aca9 --- /dev/null +++ b/server/typings/models/video/video-playlist.ts | |||
@@ -0,0 +1,92 @@ | |||
1 | import { VideoPlaylistModel } from '../../../models/video/video-playlist' | ||
2 | import { PickWith } from '../../utils' | ||
3 | import { MAccount, MAccountDefault, MAccountSummary, MAccountSummaryFormattable } from '../account' | ||
4 | import { MThumbnail } from './thumbnail' | ||
5 | import { MChannelDefault, MChannelSummary, MChannelSummaryFormattable, MChannelUrl } from './video-channels' | ||
6 | import { MVideoPlaylistElementLight } from '@server/typings/models/video/video-playlist-element' | ||
7 | |||
8 | type Use<K extends keyof VideoPlaylistModel, M> = PickWith<VideoPlaylistModel, K, M> | ||
9 | |||
10 | // ############################################################################ | ||
11 | |||
12 | export type MVideoPlaylist = Omit<VideoPlaylistModel, 'OwnerAccount' | 'VideoChannel' | 'VideoPlaylistElements' | 'Thumbnail'> | ||
13 | |||
14 | // ############################################################################ | ||
15 | |||
16 | export type MVideoPlaylistId = Pick<MVideoPlaylist, 'id'> | ||
17 | export type MVideoPlaylistPrivacy = Pick<MVideoPlaylist, 'privacy'> | ||
18 | export type MVideoPlaylistUUID = Pick<MVideoPlaylist, 'uuid'> | ||
19 | export type MVideoPlaylistVideosLength = MVideoPlaylist & { videosLength?: number } | ||
20 | |||
21 | // ############################################################################ | ||
22 | |||
23 | // With elements | ||
24 | |||
25 | export type MVideoPlaylistWithElements = MVideoPlaylist & | ||
26 | Use<'VideoPlaylistElements', MVideoPlaylistElementLight[]> | ||
27 | |||
28 | export type MVideoPlaylistIdWithElements = MVideoPlaylistId & | ||
29 | Use<'VideoPlaylistElements', MVideoPlaylistElementLight[]> | ||
30 | |||
31 | // ############################################################################ | ||
32 | |||
33 | // With account | ||
34 | |||
35 | export type MVideoPlaylistOwner = MVideoPlaylist & | ||
36 | Use<'OwnerAccount', MAccount> | ||
37 | |||
38 | export type MVideoPlaylistOwnerDefault = MVideoPlaylist & | ||
39 | Use<'OwnerAccount', MAccountDefault> | ||
40 | |||
41 | // ############################################################################ | ||
42 | |||
43 | // With thumbnail | ||
44 | |||
45 | export type MVideoPlaylistThumbnail = MVideoPlaylist & | ||
46 | Use<'Thumbnail', MThumbnail> | ||
47 | |||
48 | export type MVideoPlaylistAccountThumbnail = MVideoPlaylist & | ||
49 | Use<'OwnerAccount', MAccountDefault> & | ||
50 | Use<'Thumbnail', MThumbnail> | ||
51 | |||
52 | // ############################################################################ | ||
53 | |||
54 | // With channel | ||
55 | |||
56 | export type MVideoPlaylistAccountChannelDefault = MVideoPlaylist & | ||
57 | Use<'OwnerAccount', MAccountDefault> & | ||
58 | Use<'VideoChannel', MChannelDefault> | ||
59 | |||
60 | // ############################################################################ | ||
61 | |||
62 | // With all associations | ||
63 | |||
64 | export type MVideoPlaylistFull = MVideoPlaylist & | ||
65 | Use<'OwnerAccount', MAccountDefault> & | ||
66 | Use<'VideoChannel', MChannelDefault> & | ||
67 | Use<'Thumbnail', MThumbnail> | ||
68 | |||
69 | // ############################################################################ | ||
70 | |||
71 | // For API | ||
72 | |||
73 | export type MVideoPlaylistAccountChannelSummary = MVideoPlaylist & | ||
74 | Use<'OwnerAccount', MAccountSummary> & | ||
75 | Use<'VideoChannel', MChannelSummary> | ||
76 | |||
77 | export type MVideoPlaylistFullSummary = MVideoPlaylist & | ||
78 | Use<'Thumbnail', MThumbnail> & | ||
79 | Use<'OwnerAccount', MAccountSummary> & | ||
80 | Use<'VideoChannel', MChannelSummary> | ||
81 | |||
82 | // ############################################################################ | ||
83 | |||
84 | // Format for API or AP object | ||
85 | |||
86 | export type MVideoPlaylistFormattable = MVideoPlaylistVideosLength & | ||
87 | Use<'OwnerAccount', MAccountSummaryFormattable> & | ||
88 | Use<'VideoChannel', MChannelSummaryFormattable> | ||
89 | |||
90 | export type MVideoPlaylistAP = MVideoPlaylist & | ||
91 | Use<'Thumbnail', MThumbnail> & | ||
92 | Use<'VideoChannel', MChannelUrl> | ||
diff --git a/server/typings/models/video/video-rate.ts b/server/typings/models/video/video-rate.ts new file mode 100644 index 000000000..2ff8a625b --- /dev/null +++ b/server/typings/models/video/video-rate.ts | |||
@@ -0,0 +1,23 @@ | |||
1 | import { AccountVideoRateModel } from '@server/models/account/account-video-rate' | ||
2 | import { PickWith } from '@server/typings/utils' | ||
3 | import { MAccountAudience, MAccountUrl, MVideo, MVideoFormattable } from '..' | ||
4 | |||
5 | type Use<K extends keyof AccountVideoRateModel, M> = PickWith<AccountVideoRateModel, K, M> | ||
6 | |||
7 | // ############################################################################ | ||
8 | |||
9 | export type MAccountVideoRate = Omit<AccountVideoRateModel, 'Video' | 'Account'> | ||
10 | |||
11 | export type MAccountVideoRateAccountUrl = MAccountVideoRate & | ||
12 | Use<'Account', MAccountUrl> | ||
13 | |||
14 | export type MAccountVideoRateAccountVideo = MAccountVideoRate & | ||
15 | Use<'Account', MAccountAudience> & | ||
16 | Use<'Video', MVideo> | ||
17 | |||
18 | // ############################################################################ | ||
19 | |||
20 | // Format for API or AP object | ||
21 | |||
22 | export type MAccountVideoRateFormattable = Pick<MAccountVideoRate, 'type'> & | ||
23 | Use<'Video', MVideoFormattable> | ||
diff --git a/server/typings/models/video/video-redundancy.ts b/server/typings/models/video/video-redundancy.ts new file mode 100644 index 000000000..f3846afd7 --- /dev/null +++ b/server/typings/models/video/video-redundancy.ts | |||
@@ -0,0 +1,38 @@ | |||
1 | import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy' | ||
2 | import { PickWith, PickWithOpt } from '@server/typings/utils' | ||
3 | import { MStreamingPlaylistVideo, MVideoFile, MVideoFileVideo, MVideoUrl } from '@server/typings/models' | ||
4 | import { VideoStreamingPlaylist } from '../../../../shared/models/videos/video-streaming-playlist.model' | ||
5 | import { VideoStreamingPlaylistModel } from '@server/models/video/video-streaming-playlist' | ||
6 | import { VideoFile } from '../../../../shared/models/videos' | ||
7 | import { VideoFileModel } from '@server/models/video/video-file' | ||
8 | |||
9 | type Use<K extends keyof VideoRedundancyModel, M> = PickWith<VideoRedundancyModel, K, M> | ||
10 | |||
11 | // ############################################################################ | ||
12 | |||
13 | export type MVideoRedundancy = Omit<VideoRedundancyModel, 'VideoFile' | 'VideoStreamingPlaylist' | 'Actor'> | ||
14 | |||
15 | export type MVideoRedundancyFileUrl = Pick<MVideoRedundancy, 'fileUrl'> | ||
16 | |||
17 | // ############################################################################ | ||
18 | |||
19 | export type MVideoRedundancyFile = MVideoRedundancy & | ||
20 | Use<'VideoFile', MVideoFile> | ||
21 | |||
22 | export type MVideoRedundancyFileVideo = MVideoRedundancy & | ||
23 | Use<'VideoFile', MVideoFileVideo> | ||
24 | |||
25 | export type MVideoRedundancyStreamingPlaylistVideo = MVideoRedundancy & | ||
26 | Use<'VideoStreamingPlaylist', MStreamingPlaylistVideo> | ||
27 | |||
28 | export type MVideoRedundancyVideo = MVideoRedundancy & | ||
29 | Use<'VideoFile', MVideoFileVideo> & | ||
30 | Use<'VideoStreamingPlaylist', MStreamingPlaylistVideo> | ||
31 | |||
32 | // ############################################################################ | ||
33 | |||
34 | // Format for API or AP object | ||
35 | |||
36 | export type MVideoRedundancyAP = MVideoRedundancy & | ||
37 | PickWithOpt<VideoRedundancyModel, 'VideoFile', MVideoFile & PickWith<VideoFileModel, 'Video', MVideoUrl>> & | ||
38 | PickWithOpt<VideoRedundancyModel, 'VideoStreamingPlaylist', PickWith<VideoStreamingPlaylistModel, 'Video', MVideoUrl>> | ||
diff --git a/server/typings/models/video/video-share.ts b/server/typings/models/video/video-share.ts new file mode 100644 index 000000000..a7a90beeb --- /dev/null +++ b/server/typings/models/video/video-share.ts | |||
@@ -0,0 +1,17 @@ | |||
1 | import { VideoShareModel } from '../../../models/video/video-share' | ||
2 | import { PickWith } from '../../utils' | ||
3 | import { MActorDefault } from '../account' | ||
4 | import { MVideo } from './video' | ||
5 | |||
6 | type Use<K extends keyof VideoShareModel, M> = PickWith<VideoShareModel, K, M> | ||
7 | |||
8 | // ############################################################################ | ||
9 | |||
10 | export type MVideoShare = Omit<VideoShareModel, 'Actor' | 'Video'> | ||
11 | |||
12 | export type MVideoShareActor = MVideoShare & | ||
13 | Use<'Actor', MActorDefault> | ||
14 | |||
15 | export type MVideoShareFull = MVideoShare & | ||
16 | Use<'Actor', MActorDefault> & | ||
17 | Use<'Video', MVideo> | ||
diff --git a/server/typings/models/video/video-streaming-playlist.ts b/server/typings/models/video/video-streaming-playlist.ts new file mode 100644 index 000000000..79696bcff --- /dev/null +++ b/server/typings/models/video/video-streaming-playlist.ts | |||
@@ -0,0 +1,19 @@ | |||
1 | import { VideoStreamingPlaylistModel } from '../../../models/video/video-streaming-playlist' | ||
2 | import { PickWith, PickWithOpt } from '../../utils' | ||
3 | import { MVideoRedundancyFileUrl } from './video-redundancy' | ||
4 | import { MVideo, MVideoUrl } from '@server/typings/models' | ||
5 | |||
6 | type Use<K extends keyof VideoStreamingPlaylistModel, M> = PickWith<VideoStreamingPlaylistModel, K, M> | ||
7 | |||
8 | // ############################################################################ | ||
9 | |||
10 | export type MStreamingPlaylist = Omit<VideoStreamingPlaylistModel, 'Video' | 'RedundancyVideos'> | ||
11 | |||
12 | export type MStreamingPlaylistVideo = MStreamingPlaylist & | ||
13 | Use<'Video', MVideo> | ||
14 | |||
15 | export type MStreamingPlaylistRedundancies = MStreamingPlaylist & | ||
16 | Use<'RedundancyVideos', MVideoRedundancyFileUrl[]> | ||
17 | |||
18 | export type MStreamingPlaylistRedundanciesOpt = MStreamingPlaylist & | ||
19 | PickWithOpt<VideoStreamingPlaylistModel, 'RedundancyVideos', MVideoRedundancyFileUrl[]> | ||
diff --git a/server/typings/models/video/video.ts b/server/typings/models/video/video.ts new file mode 100644 index 000000000..9a53bd337 --- /dev/null +++ b/server/typings/models/video/video.ts | |||
@@ -0,0 +1,173 @@ | |||
1 | import { VideoModel } from '../../../models/video/video' | ||
2 | import { PickWith, PickWithOpt } from '../../utils' | ||
3 | import { | ||
4 | MChannelAccountDefault, | ||
5 | MChannelAccountLight, | ||
6 | MChannelAccountSummaryFormattable, | ||
7 | MChannelActor, | ||
8 | MChannelFormattable, | ||
9 | MChannelUserId | ||
10 | } from './video-channels' | ||
11 | import { MTag } from './tag' | ||
12 | import { MVideoCaptionLanguage } from './video-caption' | ||
13 | import { MStreamingPlaylist, MStreamingPlaylistRedundancies, MStreamingPlaylistRedundanciesOpt } from './video-streaming-playlist' | ||
14 | import { MVideoFile, MVideoFileRedundanciesOpt } from './video-file' | ||
15 | import { MThumbnail } from './thumbnail' | ||
16 | import { MVideoBlacklist, MVideoBlacklistLight, MVideoBlacklistUnfederated } from './video-blacklist' | ||
17 | import { MScheduleVideoUpdate } from './schedule-video-update' | ||
18 | import { MUserVideoHistoryTime } from '../user/user-video-history' | ||
19 | |||
20 | type Use<K extends keyof VideoModel, M> = PickWith<VideoModel, K, M> | ||
21 | |||
22 | // ############################################################################ | ||
23 | |||
24 | export type MVideo = Omit<VideoModel, 'VideoChannel' | 'Tags' | 'Thumbnails' | 'VideoPlaylistElements' | 'VideoAbuses' | | ||
25 | 'VideoFiles' | 'VideoStreamingPlaylists' | 'VideoShares' | 'AccountVideoRates' | 'VideoComments' | 'VideoViews' | 'UserVideoHistories' | | ||
26 | 'ScheduleVideoUpdate' | 'VideoBlacklist' | 'VideoImport' | 'VideoCaptions'> | ||
27 | |||
28 | // ############################################################################ | ||
29 | |||
30 | export type MVideoId = Pick<MVideo, 'id'> | ||
31 | export type MVideoUrl = Pick<MVideo, 'url'> | ||
32 | export type MVideoUUID = Pick<MVideo, 'uuid'> | ||
33 | |||
34 | export type MVideoIdUrl = MVideoId & MVideoUrl | ||
35 | export type MVideoFeed = Pick<MVideo, 'name' | 'uuid'> | ||
36 | |||
37 | // ############################################################################ | ||
38 | |||
39 | // Video raw associations: schedules, video files, tags, thumbnails, captions, streaming playlists | ||
40 | |||
41 | // "With" to not confuse with the VideoFile model | ||
42 | export type MVideoWithFile = MVideo & | ||
43 | Use<'VideoFiles', MVideoFile[]> | ||
44 | |||
45 | export type MVideoThumbnail = MVideo & | ||
46 | Use<'Thumbnails', MThumbnail[]> | ||
47 | |||
48 | export type MVideoIdThumbnail = MVideoId & | ||
49 | Use<'Thumbnails', MThumbnail[]> | ||
50 | |||
51 | export type MVideoWithFileThumbnail = MVideo & | ||
52 | Use<'VideoFiles', MVideoFile[]> & | ||
53 | Use<'Thumbnails', MThumbnail[]> | ||
54 | |||
55 | export type MVideoThumbnailBlacklist = MVideo & | ||
56 | Use<'Thumbnails', MThumbnail[]> & | ||
57 | Use<'VideoBlacklist', MVideoBlacklistLight> | ||
58 | |||
59 | export type MVideoTag = MVideo & | ||
60 | Use<'Tags', MTag[]> | ||
61 | |||
62 | export type MVideoWithSchedule = MVideo & | ||
63 | PickWithOpt<VideoModel, 'ScheduleVideoUpdate', MScheduleVideoUpdate> | ||
64 | |||
65 | export type MVideoWithCaptions = MVideo & | ||
66 | Use<'VideoCaptions', MVideoCaptionLanguage[]> | ||
67 | |||
68 | export type MVideoWithStreamingPlaylist = MVideo & | ||
69 | Use<'VideoStreamingPlaylists', MStreamingPlaylist[]> | ||
70 | |||
71 | // ############################################################################ | ||
72 | |||
73 | // Associations with not all their attributes | ||
74 | |||
75 | export type MVideoUserHistory = MVideo & | ||
76 | Use<'UserVideoHistories', MUserVideoHistoryTime[]> | ||
77 | |||
78 | export type MVideoWithBlacklistLight = MVideo & | ||
79 | Use<'VideoBlacklist', MVideoBlacklistLight> | ||
80 | |||
81 | export type MVideoAccountLight = MVideo & | ||
82 | Use<'VideoChannel', MChannelAccountLight> | ||
83 | |||
84 | export type MVideoWithRights = MVideo & | ||
85 | Use<'VideoBlacklist', MVideoBlacklistLight> & | ||
86 | Use<'Thumbnails', MThumbnail[]> & | ||
87 | Use<'VideoChannel', MChannelUserId> | ||
88 | |||
89 | // ############################################################################ | ||
90 | |||
91 | // All files with some additional associations | ||
92 | |||
93 | export type MVideoWithAllFiles = MVideo & | ||
94 | Use<'VideoFiles', MVideoFile[]> & | ||
95 | Use<'Thumbnails', MThumbnail[]> & | ||
96 | Use<'VideoStreamingPlaylists', MStreamingPlaylist[]> | ||
97 | |||
98 | export type MVideoAccountLightBlacklistAllFiles = MVideo & | ||
99 | Use<'VideoFiles', MVideoFile[]> & | ||
100 | Use<'Thumbnails', MThumbnail[]> & | ||
101 | Use<'VideoStreamingPlaylists', MStreamingPlaylist[]> & | ||
102 | Use<'VideoChannel', MChannelAccountLight> & | ||
103 | Use<'VideoBlacklist', MVideoBlacklistLight> | ||
104 | |||
105 | // ############################################################################ | ||
106 | |||
107 | // With account | ||
108 | |||
109 | export type MVideoAccountDefault = MVideo & | ||
110 | Use<'VideoChannel', MChannelAccountDefault> | ||
111 | |||
112 | export type MVideoThumbnailAccountDefault = MVideo & | ||
113 | Use<'Thumbnails', MThumbnail[]> & | ||
114 | Use<'VideoChannel', MChannelAccountDefault> | ||
115 | |||
116 | export type MVideoWithChannelActor = MVideo & | ||
117 | Use<'VideoChannel', MChannelActor> | ||
118 | |||
119 | export type MVideoFullLight = MVideo & | ||
120 | Use<'Thumbnails', MThumbnail[]> & | ||
121 | Use<'VideoBlacklist', MVideoBlacklistLight> & | ||
122 | Use<'Tags', MTag[]> & | ||
123 | Use<'VideoChannel', MChannelAccountLight> & | ||
124 | Use<'UserVideoHistories', MUserVideoHistoryTime[]> & | ||
125 | Use<'VideoFiles', MVideoFile[]> & | ||
126 | Use<'ScheduleVideoUpdate', MScheduleVideoUpdate> & | ||
127 | Use<'VideoStreamingPlaylists', MStreamingPlaylist[]> | ||
128 | |||
129 | // ############################################################################ | ||
130 | |||
131 | // API | ||
132 | |||
133 | export type MVideoAP = MVideo & | ||
134 | Use<'Tags', MTag[]> & | ||
135 | Use<'VideoChannel', MChannelAccountLight> & | ||
136 | Use<'VideoStreamingPlaylists', MStreamingPlaylist[]> & | ||
137 | Use<'VideoCaptions', MVideoCaptionLanguage[]> & | ||
138 | Use<'VideoBlacklist', MVideoBlacklistUnfederated> & | ||
139 | Use<'VideoFiles', MVideoFileRedundanciesOpt[]> | ||
140 | |||
141 | export type MVideoAPWithoutCaption = Omit<MVideoAP, 'VideoCaptions'> | ||
142 | |||
143 | export type MVideoDetails = MVideo & | ||
144 | Use<'VideoBlacklist', MVideoBlacklistLight> & | ||
145 | Use<'Tags', MTag[]> & | ||
146 | Use<'VideoChannel', MChannelAccountLight> & | ||
147 | Use<'ScheduleVideoUpdate', MScheduleVideoUpdate> & | ||
148 | Use<'Thumbnails', MThumbnail[]> & | ||
149 | Use<'UserVideoHistories', MUserVideoHistoryTime[]> & | ||
150 | Use<'VideoStreamingPlaylists', MStreamingPlaylistRedundancies[]> & | ||
151 | Use<'VideoFiles', MVideoFileRedundanciesOpt[]> | ||
152 | |||
153 | export type MVideoForUser = MVideo & | ||
154 | Use<'VideoChannel', MChannelAccountDefault> & | ||
155 | Use<'ScheduleVideoUpdate', MScheduleVideoUpdate> & | ||
156 | Use<'VideoBlacklist', MVideoBlacklistLight> & | ||
157 | Use<'Thumbnails', MThumbnail[]> | ||
158 | |||
159 | // ############################################################################ | ||
160 | |||
161 | // Format for API or AP object | ||
162 | |||
163 | export type MVideoFormattable = MVideo & | ||
164 | PickWithOpt<VideoModel, 'UserVideoHistories', MUserVideoHistoryTime[]> & | ||
165 | Use<'VideoChannel', MChannelAccountSummaryFormattable> & | ||
166 | PickWithOpt<VideoModel, 'ScheduleVideoUpdate', Pick<MScheduleVideoUpdate, 'updateAt' | 'privacy'>> & | ||
167 | PickWithOpt<VideoModel, 'VideoBlacklist', Pick<MVideoBlacklist, 'reason'>> | ||
168 | |||
169 | export type MVideoFormattableDetails = MVideoFormattable & | ||
170 | Use<'VideoChannel', MChannelFormattable> & | ||
171 | Use<'Tags', MTag[]> & | ||
172 | Use<'VideoStreamingPlaylists', MStreamingPlaylistRedundanciesOpt[]> & | ||
173 | Use<'VideoFiles', MVideoFileRedundanciesOpt[]> | ||
diff --git a/server/typings/utils.ts b/server/typings/utils.ts index a86b05be2..24d43b258 100644 --- a/server/typings/utils.ts +++ b/server/typings/utils.ts | |||
@@ -1,3 +1,22 @@ | |||
1 | export type FunctionPropertyNames<T> = { [K in keyof T]: T[K] extends Function ? K : never }[keyof T] | 1 | export type FunctionPropertyNames<T> = { |
2 | [K in keyof T]: T[K] extends Function ? K : never | ||
3 | }[keyof T] | ||
2 | 4 | ||
3 | export type FunctionProperties<T> = Pick<T, FunctionPropertyNames<T>> | 5 | export type FunctionProperties<T> = Pick<T, FunctionPropertyNames<T>> |
6 | |||
7 | export type PickWith<T, KT extends keyof T, V> = { | ||
8 | [P in KT]: T[P] extends V ? V : never | ||
9 | } | ||
10 | |||
11 | export type PickWithOpt<T, KT extends keyof T, V> = { | ||
12 | [P in KT]?: T[P] extends V ? V : never | ||
13 | } | ||
14 | |||
15 | // https://github.com/krzkaczor/ts-essentials Rocks! | ||
16 | export type DeepPartial<T> = { | ||
17 | [P in keyof T]?: T[P] extends Array<infer U> | ||
18 | ? Array<DeepPartial<U>> | ||
19 | : T[P] extends ReadonlyArray<infer U> | ||
20 | ? ReadonlyArray<DeepPartial<U>> | ||
21 | : DeepPartial<T[P]> | ||
22 | } | ||
diff --git a/shared/extra-utils/index.ts b/shared/extra-utils/index.ts index 53ddaa681..78acf72aa 100644 --- a/shared/extra-utils/index.ts +++ b/shared/extra-utils/index.ts | |||
@@ -24,4 +24,5 @@ export * from './videos/video-streaming-playlists' | |||
24 | export * from './videos/videos' | 24 | export * from './videos/videos' |
25 | export * from './videos/video-change-ownership' | 25 | export * from './videos/video-change-ownership' |
26 | export * from './feeds/feeds' | 26 | export * from './feeds/feeds' |
27 | export * from './instances-index/mock-instances-index' | ||
27 | export * from './search/videos' | 28 | export * from './search/videos' |
diff --git a/shared/extra-utils/instances-index/mock-instances-index.ts b/shared/extra-utils/instances-index/mock-instances-index.ts new file mode 100644 index 000000000..cfa4523c1 --- /dev/null +++ b/shared/extra-utils/instances-index/mock-instances-index.ts | |||
@@ -0,0 +1,38 @@ | |||
1 | import * as express from 'express' | ||
2 | |||
3 | export class MockInstancesIndex { | ||
4 | private indexInstances: { host: string, createdAt: string }[] = [] | ||
5 | |||
6 | initialize () { | ||
7 | return new Promise(res => { | ||
8 | const app = express() | ||
9 | |||
10 | app.use('/', (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
11 | if (process.env.DEBUG) console.log('Receiving request on mocked server %s.', req.url) | ||
12 | |||
13 | return next() | ||
14 | }) | ||
15 | |||
16 | app.get('/api/v1/instances/hosts', (req: express.Request, res: express.Response) => { | ||
17 | const since = req.query.since | ||
18 | |||
19 | const filtered = this.indexInstances.filter(i => { | ||
20 | if (!since) return true | ||
21 | |||
22 | return i.createdAt > since | ||
23 | }) | ||
24 | |||
25 | return res.json({ | ||
26 | total: filtered.length, | ||
27 | data: filtered | ||
28 | }) | ||
29 | }) | ||
30 | |||
31 | app.listen(42100, () => res()) | ||
32 | }) | ||
33 | } | ||
34 | |||
35 | addInstance (host: string) { | ||
36 | this.indexInstances.push({ host, createdAt: new Date().toISOString() }) | ||
37 | } | ||
38 | } | ||
diff --git a/shared/extra-utils/server/config.ts b/shared/extra-utils/server/config.ts index 8736f083f..578dd35cf 100644 --- a/shared/extra-utils/server/config.ts +++ b/shared/extra-utils/server/config.ts | |||
@@ -1,5 +1,7 @@ | |||
1 | import { makeDeleteRequest, makeGetRequest, makePutBodyRequest } from '../requests/requests' | 1 | import { makeDeleteRequest, makeGetRequest, makePutBodyRequest } from '../requests/requests' |
2 | import { CustomConfig } from '../../models/server/custom-config.model' | 2 | import { CustomConfig } from '../../models/server/custom-config.model' |
3 | import { DeepPartial } from '@server/typings/utils' | ||
4 | import { merge } from 'lodash' | ||
3 | 5 | ||
4 | function getConfig (url: string) { | 6 | function getConfig (url: string) { |
5 | const path = '/api/v1/config' | 7 | const path = '/api/v1/config' |
@@ -44,13 +46,25 @@ function updateCustomConfig (url: string, token: string, newCustomConfig: Custom | |||
44 | }) | 46 | }) |
45 | } | 47 | } |
46 | 48 | ||
47 | function updateCustomSubConfig (url: string, token: string, newConfig: any) { | 49 | function updateCustomSubConfig (url: string, token: string, newConfig: DeepPartial<CustomConfig>) { |
48 | const updateParams: CustomConfig = { | 50 | const updateParams: CustomConfig = { |
49 | instance: { | 51 | instance: { |
50 | name: 'PeerTube updated', | 52 | name: 'PeerTube updated', |
51 | shortDescription: 'my short description', | 53 | shortDescription: 'my short description', |
52 | description: 'my super description', | 54 | description: 'my super description', |
53 | terms: 'my super terms', | 55 | terms: 'my super terms', |
56 | codeOfConduct: 'my super coc', | ||
57 | |||
58 | creationReason: 'my super creation reason', | ||
59 | moderationInformation: 'my super moderation information', | ||
60 | administrator: 'Kuja', | ||
61 | maintenanceLifetime: 'forever', | ||
62 | businessModel: 'my super business model', | ||
63 | hardwareInformation: '2vCore 3GB RAM', | ||
64 | |||
65 | languages: [ 'en', 'es' ], | ||
66 | categories: [ 1, 2 ], | ||
67 | |||
54 | defaultClientRoute: '/videos/recently-added', | 68 | defaultClientRoute: '/videos/recently-added', |
55 | isNSFW: true, | 69 | isNSFW: true, |
56 | defaultNSFWPolicy: 'blur', | 70 | defaultNSFWPolicy: 'blur', |
@@ -130,10 +144,21 @@ function updateCustomSubConfig (url: string, token: string, newConfig: any) { | |||
130 | enabled: true, | 144 | enabled: true, |
131 | manualApproval: false | 145 | manualApproval: false |
132 | } | 146 | } |
147 | }, | ||
148 | followings: { | ||
149 | instance: { | ||
150 | autoFollowBack: { | ||
151 | enabled: false | ||
152 | }, | ||
153 | autoFollowIndex: { | ||
154 | indexUrl: 'https://instances.joinpeertube.org', | ||
155 | enabled: false | ||
156 | } | ||
157 | } | ||
133 | } | 158 | } |
134 | } | 159 | } |
135 | 160 | ||
136 | Object.assign(updateParams, newConfig) | 161 | merge(updateParams, newConfig) |
137 | 162 | ||
138 | return updateCustomConfig(url, token, updateParams) | 163 | return updateCustomConfig(url, token, updateParams) |
139 | } | 164 | } |
diff --git a/shared/extra-utils/users/user-notifications.ts b/shared/extra-utils/users/user-notifications.ts index f7de542bf..9a5fd7e86 100644 --- a/shared/extra-utils/users/user-notifications.ts +++ b/shared/extra-utils/users/user-notifications.ts | |||
@@ -279,8 +279,9 @@ async function checkNewActorFollow ( | |||
279 | expect(notification.actorFollow.follower.name).to.equal(followerName) | 279 | expect(notification.actorFollow.follower.name).to.equal(followerName) |
280 | expect(notification.actorFollow.follower.host).to.not.be.undefined | 280 | expect(notification.actorFollow.follower.host).to.not.be.undefined |
281 | 281 | ||
282 | expect(notification.actorFollow.following.displayName).to.equal(followingDisplayName) | 282 | const following = notification.actorFollow.following |
283 | expect(notification.actorFollow.following.type).to.equal(followType) | 283 | expect(following.displayName).to.equal(followingDisplayName) |
284 | expect(following.type).to.equal(followType) | ||
284 | } else { | 285 | } else { |
285 | expect(notification).to.satisfy(n => { | 286 | expect(notification).to.satisfy(n => { |
286 | return n.type !== notificationType || | 287 | return n.type !== notificationType || |
@@ -327,6 +328,37 @@ async function checkNewInstanceFollower (base: CheckerBaseParams, followerHost: | |||
327 | await checkNotification(base, notificationChecker, emailFinder, type) | 328 | await checkNotification(base, notificationChecker, emailFinder, type) |
328 | } | 329 | } |
329 | 330 | ||
331 | async function checkAutoInstanceFollowing (base: CheckerBaseParams, followerHost: string, followingHost: string, type: CheckerType) { | ||
332 | const notificationType = UserNotificationType.AUTO_INSTANCE_FOLLOWING | ||
333 | |||
334 | function notificationChecker (notification: UserNotification, type: CheckerType) { | ||
335 | if (type === 'presence') { | ||
336 | expect(notification).to.not.be.undefined | ||
337 | expect(notification.type).to.equal(notificationType) | ||
338 | |||
339 | const following = notification.actorFollow.following | ||
340 | checkActor(following) | ||
341 | expect(following.name).to.equal('peertube') | ||
342 | expect(following.host).to.equal(followingHost) | ||
343 | |||
344 | expect(notification.actorFollow.follower.name).to.equal('peertube') | ||
345 | expect(notification.actorFollow.follower.host).to.equal(followerHost) | ||
346 | } else { | ||
347 | expect(notification).to.satisfy(n => { | ||
348 | return n.type !== notificationType || n.actorFollow.following.host !== followingHost | ||
349 | }) | ||
350 | } | ||
351 | } | ||
352 | |||
353 | function emailFinder (email: object) { | ||
354 | const text: string = email[ 'text' ] | ||
355 | |||
356 | return text.includes(' automatically followed a new instance') && text.includes(followingHost) | ||
357 | } | ||
358 | |||
359 | await checkNotification(base, notificationChecker, emailFinder, type) | ||
360 | } | ||
361 | |||
330 | async function checkCommentMention ( | 362 | async function checkCommentMention ( |
331 | base: CheckerBaseParams, | 363 | base: CheckerBaseParams, |
332 | uuid: string, | 364 | uuid: string, |
@@ -427,8 +459,8 @@ async function checkVideoAutoBlacklistForModerators (base: CheckerBaseParams, vi | |||
427 | expect(notification).to.not.be.undefined | 459 | expect(notification).to.not.be.undefined |
428 | expect(notification.type).to.equal(notificationType) | 460 | expect(notification.type).to.equal(notificationType) |
429 | 461 | ||
430 | expect(notification.video.id).to.be.a('number') | 462 | expect(notification.videoBlacklist.video.id).to.be.a('number') |
431 | checkVideo(notification.video, videoName, videoUUID) | 463 | checkVideo(notification.videoBlacklist.video, videoName, videoUUID) |
432 | } else { | 464 | } else { |
433 | expect(notification).to.satisfy((n: UserNotification) => { | 465 | expect(notification).to.satisfy((n: UserNotification) => { |
434 | return n === undefined || n.video === undefined || n.video.uuid !== videoUUID | 466 | return n === undefined || n.video === undefined || n.video.uuid !== videoUUID |
@@ -480,6 +512,7 @@ export { | |||
480 | markAsReadAllNotifications, | 512 | markAsReadAllNotifications, |
481 | checkMyVideoImportIsFinished, | 513 | checkMyVideoImportIsFinished, |
482 | checkUserRegistered, | 514 | checkUserRegistered, |
515 | checkAutoInstanceFollowing, | ||
483 | checkVideoIsPublished, | 516 | checkVideoIsPublished, |
484 | checkNewVideoFromSubscription, | 517 | checkNewVideoFromSubscription, |
485 | checkNewActorFollow, | 518 | checkNewActorFollow, |
diff --git a/shared/extra-utils/users/users.ts b/shared/extra-utils/users/users.ts index 30ed1bf4a..9959fd074 100644 --- a/shared/extra-utils/users/users.ts +++ b/shared/extra-utils/users/users.ts | |||
@@ -1,12 +1,12 @@ | |||
1 | import * as request from 'supertest' | 1 | import * as request from 'supertest' |
2 | import { makePostBodyRequest, makePutBodyRequest, updateAvatarRequest } from '../requests/requests' | 2 | import { makePostBodyRequest, makePutBodyRequest, updateAvatarRequest } from '../requests/requests' |
3 | import { NSFWPolicyType } from '../../models/videos/nsfw-policy.type' | ||
4 | import { UserAdminFlag } from '../../models/users/user-flag.model' | 3 | import { UserAdminFlag } from '../../models/users/user-flag.model' |
5 | import { UserRegister } from '../../models/users/user-register.model' | 4 | import { UserRegister } from '../../models/users/user-register.model' |
6 | import { UserRole } from '../../models/users/user-role' | 5 | import { UserRole } from '../../models/users/user-role' |
7 | import { ServerInfo } from '../server/servers' | 6 | import { ServerInfo } from '../server/servers' |
8 | import { userLogin } from './login' | 7 | import { userLogin } from './login' |
9 | import { UserUpdateMe } from '../../models/users' | 8 | import { UserUpdateMe } from '../../models/users' |
9 | import { omit } from 'lodash' | ||
10 | 10 | ||
11 | type CreateUserArgs = { url: string, | 11 | type CreateUserArgs = { url: string, |
12 | accessToken: string, | 12 | accessToken: string, |
@@ -214,33 +214,10 @@ function unblockUser (url: string, userId: number | string, accessToken: string, | |||
214 | .expect(expectedStatus) | 214 | .expect(expectedStatus) |
215 | } | 215 | } |
216 | 216 | ||
217 | function updateMyUser (options: { | 217 | function updateMyUser (options: { url: string, accessToken: string } & UserUpdateMe) { |
218 | url: string | ||
219 | accessToken: string | ||
220 | currentPassword?: string | ||
221 | newPassword?: string | ||
222 | nsfwPolicy?: NSFWPolicyType | ||
223 | email?: string | ||
224 | autoPlayVideo?: boolean | ||
225 | displayName?: string | ||
226 | description?: string | ||
227 | videosHistoryEnabled?: boolean | ||
228 | theme?: string | ||
229 | }) { | ||
230 | const path = '/api/v1/users/me' | 218 | const path = '/api/v1/users/me' |
231 | 219 | ||
232 | const toSend: UserUpdateMe = {} | 220 | const toSend: UserUpdateMe = omit(options, 'url', 'accessToken') |
233 | if (options.currentPassword !== undefined && options.currentPassword !== null) toSend.currentPassword = options.currentPassword | ||
234 | if (options.newPassword !== undefined && options.newPassword !== null) toSend.password = options.newPassword | ||
235 | if (options.nsfwPolicy !== undefined && options.nsfwPolicy !== null) toSend.nsfwPolicy = options.nsfwPolicy | ||
236 | if (options.autoPlayVideo !== undefined && options.autoPlayVideo !== null) toSend.autoPlayVideo = options.autoPlayVideo | ||
237 | if (options.email !== undefined && options.email !== null) toSend.email = options.email | ||
238 | if (options.description !== undefined && options.description !== null) toSend.description = options.description | ||
239 | if (options.displayName !== undefined && options.displayName !== null) toSend.displayName = options.displayName | ||
240 | if (options.theme !== undefined && options.theme !== null) toSend.theme = options.theme | ||
241 | if (options.videosHistoryEnabled !== undefined && options.videosHistoryEnabled !== null) { | ||
242 | toSend.videosHistoryEnabled = options.videosHistoryEnabled | ||
243 | } | ||
244 | 221 | ||
245 | return makePutBodyRequest({ | 222 | return makePutBodyRequest({ |
246 | url: options.url, | 223 | url: options.url, |
diff --git a/shared/models/activitypub/activity.ts b/shared/models/activitypub/activity.ts index 95801190d..492b672c7 100644 --- a/shared/models/activitypub/activity.ts +++ b/shared/models/activitypub/activity.ts | |||
@@ -91,5 +91,5 @@ export interface ActivityDislike extends BaseActivity { | |||
91 | export interface ActivityFlag extends BaseActivity { | 91 | export interface ActivityFlag extends BaseActivity { |
92 | type: 'Flag', | 92 | type: 'Flag', |
93 | content: string, | 93 | content: string, |
94 | object: APObject | 94 | object: APObject | APObject[] |
95 | } | 95 | } |
diff --git a/shared/models/activitypub/objects/video-abuse-object.ts b/shared/models/activitypub/objects/video-abuse-object.ts index 40e7abd57..5f1264a76 100644 --- a/shared/models/activitypub/objects/video-abuse-object.ts +++ b/shared/models/activitypub/objects/video-abuse-object.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | export interface VideoAbuseObject { | 1 | export interface VideoAbuseObject { |
2 | type: 'Flag', | 2 | type: 'Flag', |
3 | content: string | 3 | content: string |
4 | object: string | 4 | object: string | string[] |
5 | } | 5 | } |
diff --git a/shared/models/i18n/i18n.ts b/shared/models/i18n/i18n.ts index 218fd09ba..03a5d858a 100644 --- a/shared/models/i18n/i18n.ts +++ b/shared/models/i18n/i18n.ts | |||
@@ -39,7 +39,9 @@ const I18N_LOCALE_ALIAS = { | |||
39 | 'pl': 'pl-PL', | 39 | 'pl': 'pl-PL', |
40 | 'ru': 'ru-RU', | 40 | 'ru': 'ru-RU', |
41 | 'nl': 'nl-NL', | 41 | 'nl': 'nl-NL', |
42 | 'zh': 'zh-Hans-CN' | 42 | 'zh': 'zh-Hans-CN', |
43 | 'zh-CN': 'zh-Hans-CN', | ||
44 | 'zh-TW': 'zh-Hant-TW' | ||
43 | } | 45 | } |
44 | 46 | ||
45 | export const POSSIBLE_LOCALES = Object.keys(I18N_LOCALES) | 47 | export const POSSIBLE_LOCALES = Object.keys(I18N_LOCALES) |
diff --git a/shared/models/server/about.model.ts b/shared/models/server/about.model.ts index 10dff8b8f..6d4ba63c4 100644 --- a/shared/models/server/about.model.ts +++ b/shared/models/server/about.model.ts | |||
@@ -4,5 +4,17 @@ export interface About { | |||
4 | shortDescription: string | 4 | shortDescription: string |
5 | description: string | 5 | description: string |
6 | terms: string | 6 | terms: string |
7 | |||
8 | codeOfConduct: string | ||
9 | hardwareInformation: string | ||
10 | |||
11 | creationReason: string | ||
12 | moderationInformation: string | ||
13 | administrator: string | ||
14 | maintenanceLifetime: string | ||
15 | businessModel: string | ||
16 | |||
17 | languages: string[] | ||
18 | categories: number[] | ||
7 | } | 19 | } |
8 | } | 20 | } |
diff --git a/shared/models/server/custom-config.model.ts b/shared/models/server/custom-config.model.ts index a0541f5b6..c9957f825 100644 --- a/shared/models/server/custom-config.model.ts +++ b/shared/models/server/custom-config.model.ts | |||
@@ -6,6 +6,18 @@ export interface CustomConfig { | |||
6 | shortDescription: string | 6 | shortDescription: string |
7 | description: string | 7 | description: string |
8 | terms: string | 8 | terms: string |
9 | codeOfConduct: string | ||
10 | |||
11 | creationReason: string | ||
12 | moderationInformation: string | ||
13 | administrator: string | ||
14 | maintenanceLifetime: string | ||
15 | businessModel: string | ||
16 | hardwareInformation: string | ||
17 | |||
18 | languages: string[] | ||
19 | categories: number[] | ||
20 | |||
9 | isNSFW: boolean | 21 | isNSFW: boolean |
10 | defaultClientRoute: string | 22 | defaultClientRoute: string |
11 | defaultNSFWPolicy: NSFWPolicyType | 23 | defaultNSFWPolicy: NSFWPolicyType |
@@ -99,4 +111,16 @@ export interface CustomConfig { | |||
99 | } | 111 | } |
100 | } | 112 | } |
101 | 113 | ||
114 | followings: { | ||
115 | instance: { | ||
116 | autoFollowBack: { | ||
117 | enabled: boolean | ||
118 | } | ||
119 | |||
120 | autoFollowIndex: { | ||
121 | enabled: boolean | ||
122 | indexUrl: string | ||
123 | } | ||
124 | } | ||
125 | } | ||
102 | } | 126 | } |
diff --git a/shared/models/users/user-notification-setting.model.ts b/shared/models/users/user-notification-setting.model.ts index e2a882b69..451f40d58 100644 --- a/shared/models/users/user-notification-setting.model.ts +++ b/shared/models/users/user-notification-setting.model.ts | |||
@@ -16,4 +16,5 @@ export interface UserNotificationSetting { | |||
16 | newFollow: UserNotificationSettingValue | 16 | newFollow: UserNotificationSettingValue |
17 | commentMention: UserNotificationSettingValue | 17 | commentMention: UserNotificationSettingValue |
18 | newInstanceFollower: UserNotificationSettingValue | 18 | newInstanceFollower: UserNotificationSettingValue |
19 | autoInstanceFollowing: UserNotificationSettingValue | ||
19 | } | 20 | } |
diff --git a/shared/models/users/user-notification.model.ts b/shared/models/users/user-notification.model.ts index fafc2b7d7..e9be1ca7f 100644 --- a/shared/models/users/user-notification.model.ts +++ b/shared/models/users/user-notification.model.ts | |||
@@ -19,7 +19,9 @@ export enum UserNotificationType { | |||
19 | 19 | ||
20 | VIDEO_AUTO_BLACKLIST_FOR_MODERATORS = 12, | 20 | VIDEO_AUTO_BLACKLIST_FOR_MODERATORS = 12, |
21 | 21 | ||
22 | NEW_INSTANCE_FOLLOWER = 13 | 22 | NEW_INSTANCE_FOLLOWER = 13, |
23 | |||
24 | AUTO_INSTANCE_FOLLOWING = 14 | ||
23 | } | 25 | } |
24 | 26 | ||
25 | export interface VideoInfo { | 27 | export interface VideoInfo { |
@@ -78,10 +80,12 @@ export interface UserNotification { | |||
78 | id: number | 80 | id: number |
79 | follower: ActorInfo | 81 | follower: ActorInfo |
80 | state: FollowState | 82 | state: FollowState |
83 | |||
81 | following: { | 84 | following: { |
82 | type: 'account' | 'channel' | 85 | type: 'account' | 'channel' | 'instance' |
83 | name: string | 86 | name: string |
84 | displayName: string | 87 | displayName: string |
88 | host: string | ||
85 | } | 89 | } |
86 | } | 90 | } |
87 | 91 | ||
diff --git a/shared/models/users/user-update-me.model.ts b/shared/models/users/user-update-me.model.ts index b6c0002e5..99b9a65bd 100644 --- a/shared/models/users/user-update-me.model.ts +++ b/shared/models/users/user-update-me.model.ts | |||
@@ -15,4 +15,7 @@ export interface UserUpdateMe { | |||
15 | password?: string | 15 | password?: string |
16 | 16 | ||
17 | theme?: string | 17 | theme?: string |
18 | |||
19 | noInstanceConfigWarningModal?: boolean | ||
20 | noWelcomeModal?: boolean | ||
18 | } | 21 | } |
diff --git a/shared/models/users/user.model.ts b/shared/models/users/user.model.ts index de9825e1f..f67d262b0 100644 --- a/shared/models/users/user.model.ts +++ b/shared/models/users/user.model.ts | |||
@@ -10,6 +10,7 @@ export interface User { | |||
10 | username: string | 10 | username: string |
11 | email: string | 11 | email: string |
12 | pendingEmail: string | null | 12 | pendingEmail: string | null |
13 | |||
13 | emailVerified: boolean | 14 | emailVerified: boolean |
14 | nsfwPolicy: NSFWPolicyType | 15 | nsfwPolicy: NSFWPolicyType |
15 | 16 | ||
@@ -18,13 +19,15 @@ export interface User { | |||
18 | autoPlayVideo: boolean | 19 | autoPlayVideo: boolean |
19 | webTorrentEnabled: boolean | 20 | webTorrentEnabled: boolean |
20 | videosHistoryEnabled: boolean | 21 | videosHistoryEnabled: boolean |
22 | videoLanguages: string[] | ||
21 | 23 | ||
22 | role: UserRole | 24 | role: UserRole |
23 | roleLabel: string | 25 | roleLabel: string |
24 | 26 | ||
25 | videoQuota: number | 27 | videoQuota: number |
26 | videoQuotaDaily: number | 28 | videoQuotaDaily: number |
27 | createdAt: Date | 29 | videoQuotaUsed?: number |
30 | videoQuotaUsedDaily?: number | ||
28 | 31 | ||
29 | theme: string | 32 | theme: string |
30 | 33 | ||
@@ -35,5 +38,8 @@ export interface User { | |||
35 | blocked: boolean | 38 | blocked: boolean |
36 | blockedReason?: string | 39 | blockedReason?: string |
37 | 40 | ||
38 | videoQuotaUsed?: number | 41 | noInstanceConfigWarningModal: boolean |
42 | noWelcomeModal: boolean | ||
43 | |||
44 | createdAt: Date | ||
39 | } | 45 | } |
diff --git a/support/doc/api/openapi.yaml b/support/doc/api/openapi.yaml index 30a250c70..b0473c7e7 100644 --- a/support/doc/api/openapi.yaml +++ b/support/doc/api/openapi.yaml | |||
@@ -130,9 +130,6 @@ paths: | |||
130 | summary: Get the account by name | 130 | summary: Get the account by name |
131 | parameters: | 131 | parameters: |
132 | - $ref: '#/components/parameters/name' | 132 | - $ref: '#/components/parameters/name' |
133 | - $ref: '#/components/parameters/start' | ||
134 | - $ref: '#/components/parameters/count' | ||
135 | - $ref: '#/components/parameters/sort' | ||
136 | responses: | 133 | responses: |
137 | '200': | 134 | '200': |
138 | description: successful operation | 135 | description: successful operation |
@@ -204,6 +201,10 @@ paths: | |||
204 | tags: | 201 | tags: |
205 | - Accounts | 202 | - Accounts |
206 | summary: Get all accounts | 203 | summary: Get all accounts |
204 | parameters: | ||
205 | - $ref: '#/components/parameters/start' | ||
206 | - $ref: '#/components/parameters/count' | ||
207 | - $ref: '#/components/parameters/sort' | ||
207 | responses: | 208 | responses: |
208 | '200': | 209 | '200': |
209 | description: successful operation | 210 | description: successful operation |
@@ -233,6 +234,10 @@ paths: | |||
233 | responses: | 234 | responses: |
234 | '200': | 235 | '200': |
235 | description: successful operation | 236 | description: successful operation |
237 | content: | ||
238 | application/json: | ||
239 | schema: | ||
240 | $ref: '#/components/schemas/ServerConfigAbout' | ||
236 | /config/custom: | 241 | /config/custom: |
237 | get: | 242 | get: |
238 | summary: Get the runtime configuration of the server | 243 | summary: Get the runtime configuration of the server |
@@ -244,6 +249,10 @@ paths: | |||
244 | responses: | 249 | responses: |
245 | '200': | 250 | '200': |
246 | description: successful operation | 251 | description: successful operation |
252 | content: | ||
253 | application/json: | ||
254 | schema: | ||
255 | $ref: '#/components/schemas/ServerConfigCustom' | ||
247 | put: | 256 | put: |
248 | summary: Set the runtime configuration of the server | 257 | summary: Set the runtime configuration of the server |
249 | tags: | 258 | tags: |
@@ -726,8 +735,7 @@ paths: | |||
726 | type: string | 735 | type: string |
727 | format: binary | 736 | format: binary |
728 | encoding: | 737 | encoding: |
729 | profileImage: | 738 | avatarfile: |
730 | # only accept png/jpeg | ||
731 | contentType: image/png, image/jpeg | 739 | contentType: image/png, image/jpeg |
732 | /videos: | 740 | /videos: |
733 | get: | 741 | get: |
@@ -829,9 +837,11 @@ paths: | |||
829 | thumbnailfile: | 837 | thumbnailfile: |
830 | description: Video thumbnail file | 838 | description: Video thumbnail file |
831 | type: string | 839 | type: string |
840 | format: binary | ||
832 | previewfile: | 841 | previewfile: |
833 | description: Video preview file | 842 | description: Video preview file |
834 | type: string | 843 | type: string |
844 | format: binary | ||
835 | category: | 845 | category: |
836 | description: Video category | 846 | description: Video category |
837 | type: string | 847 | type: string |
@@ -874,6 +884,11 @@ paths: | |||
874 | format: date-time | 884 | format: date-time |
875 | scheduleUpdate: | 885 | scheduleUpdate: |
876 | $ref: '#/components/schemas/VideoScheduledUpdate' | 886 | $ref: '#/components/schemas/VideoScheduledUpdate' |
887 | encoding: | ||
888 | thumbnailfile: | ||
889 | contentType: image/jpeg | ||
890 | previewfile: | ||
891 | contentType: image/jpeg | ||
877 | get: | 892 | get: |
878 | summary: Get a video by its id | 893 | summary: Get a video by its id |
879 | tags: | 894 | tags: |
@@ -1029,9 +1044,11 @@ paths: | |||
1029 | thumbnailfile: | 1044 | thumbnailfile: |
1030 | description: Video thumbnail file | 1045 | description: Video thumbnail file |
1031 | type: string | 1046 | type: string |
1047 | format: binary | ||
1032 | previewfile: | 1048 | previewfile: |
1033 | description: Video preview file | 1049 | description: Video preview file |
1034 | type: string | 1050 | type: string |
1051 | format: binary | ||
1035 | privacy: | 1052 | privacy: |
1036 | $ref: '#/components/schemas/VideoPrivacySet' | 1053 | $ref: '#/components/schemas/VideoPrivacySet' |
1037 | category: | 1054 | category: |
@@ -1080,6 +1097,13 @@ paths: | |||
1080 | - videofile | 1097 | - videofile |
1081 | - channelId | 1098 | - channelId |
1082 | - name | 1099 | - name |
1100 | encoding: | ||
1101 | videofile: | ||
1102 | contentType: video/mp4, video/webm, video/ogg, video/avi, video/quicktime, video/x-msvideo, video/x-flv, video/x-matroska, application/octet-stream | ||
1103 | thumbnailfile: | ||
1104 | contentType: image/jpeg | ||
1105 | previewfile: | ||
1106 | contentType: image/jpeg | ||
1083 | x-code-samples: | 1107 | x-code-samples: |
1084 | - lang: Shell | 1108 | - lang: Shell |
1085 | source: | | 1109 | source: | |
@@ -1142,9 +1166,11 @@ paths: | |||
1142 | thumbnailfile: | 1166 | thumbnailfile: |
1143 | description: Video thumbnail file | 1167 | description: Video thumbnail file |
1144 | type: string | 1168 | type: string |
1169 | format: binary | ||
1145 | previewfile: | 1170 | previewfile: |
1146 | description: Video preview file | 1171 | description: Video preview file |
1147 | type: string | 1172 | type: string |
1173 | format: binary | ||
1148 | privacy: | 1174 | privacy: |
1149 | $ref: '#/components/schemas/VideoPrivacySet' | 1175 | $ref: '#/components/schemas/VideoPrivacySet' |
1150 | category: | 1176 | category: |
@@ -1188,6 +1214,13 @@ paths: | |||
1188 | required: | 1214 | required: |
1189 | - channelId | 1215 | - channelId |
1190 | - name | 1216 | - name |
1217 | encoding: | ||
1218 | torrentfile: | ||
1219 | contentType: application/x-bittorrent | ||
1220 | thumbnailfile: | ||
1221 | contentType: image/jpeg | ||
1222 | previewfile: | ||
1223 | contentType: image/jpeg | ||
1191 | /videos/abuse: | 1224 | /videos/abuse: |
1192 | get: | 1225 | get: |
1193 | summary: Get list of reported video abuses | 1226 | summary: Get list of reported video abuses |
@@ -1308,6 +1341,9 @@ paths: | |||
1308 | description: The file to upload. | 1341 | description: The file to upload. |
1309 | type: string | 1342 | type: string |
1310 | format: binary | 1343 | format: binary |
1344 | encoding: | ||
1345 | captionfile: | ||
1346 | contentType: text/vtt, application/x-subrip | ||
1311 | responses: | 1347 | responses: |
1312 | '204': | 1348 | '204': |
1313 | $ref: '#/paths/~1users~1me/put/responses/204' | 1349 | $ref: '#/paths/~1users~1me/put/responses/204' |
@@ -1952,7 +1988,7 @@ components: | |||
1952 | description: 'Video file size in bytes' | 1988 | description: 'Video file size in bytes' |
1953 | torrentUrl: | 1989 | torrentUrl: |
1954 | type: string | 1990 | type: string |
1955 | torrentDownaloadUrl: | 1991 | torrentDownloadUrl: |
1956 | type: string | 1992 | type: string |
1957 | fileUrl: | 1993 | fileUrl: |
1958 | type: string | 1994 | type: string |
@@ -2227,8 +2263,6 @@ components: | |||
2227 | properties: | 2263 | properties: |
2228 | id: | 2264 | id: |
2229 | type: number | 2265 | type: number |
2230 | uuid: | ||
2231 | type: string | ||
2232 | url: | 2266 | url: |
2233 | type: string | 2267 | type: string |
2234 | name: | 2268 | name: |
@@ -2249,8 +2283,12 @@ components: | |||
2249 | allOf: | 2283 | allOf: |
2250 | - $ref: '#/components/schemas/Actor' | 2284 | - $ref: '#/components/schemas/Actor' |
2251 | - properties: | 2285 | - properties: |
2286 | userId: | ||
2287 | type: string | ||
2252 | displayName: | 2288 | displayName: |
2253 | type: string | 2289 | type: string |
2290 | description: | ||
2291 | type: string | ||
2254 | User: | 2292 | User: |
2255 | properties: | 2293 | properties: |
2256 | id: | 2294 | id: |
@@ -2294,18 +2332,102 @@ components: | |||
2294 | type: number | 2332 | type: number |
2295 | ServerConfig: | 2333 | ServerConfig: |
2296 | properties: | 2334 | properties: |
2335 | instance: | ||
2336 | type: object | ||
2337 | properties: | ||
2338 | name: | ||
2339 | type: string | ||
2340 | shortDescription: | ||
2341 | type: string | ||
2342 | defaultClientRoute: | ||
2343 | type: string | ||
2344 | isNSFW: | ||
2345 | type: boolean | ||
2346 | defaultNSFWPolicy: | ||
2347 | type: string | ||
2348 | customizations: | ||
2349 | type: object | ||
2350 | properties: | ||
2351 | javascript: | ||
2352 | type: string | ||
2353 | css: | ||
2354 | type: string | ||
2355 | plugin: | ||
2356 | type: object | ||
2357 | properties: | ||
2358 | registered: | ||
2359 | type: array | ||
2360 | items: | ||
2361 | type: string | ||
2362 | theme: | ||
2363 | type: object | ||
2364 | properties: | ||
2365 | registered: | ||
2366 | type: array | ||
2367 | items: | ||
2368 | type: string | ||
2369 | email: | ||
2370 | type: object | ||
2371 | properties: | ||
2372 | enabled: | ||
2373 | type: boolean | ||
2374 | contactForm: | ||
2375 | type: object | ||
2376 | properties: | ||
2377 | enabled: | ||
2378 | type: boolean | ||
2379 | serverVersion: | ||
2380 | type: string | ||
2381 | serverCommit: | ||
2382 | type: string | ||
2297 | signup: | 2383 | signup: |
2298 | type: object | 2384 | type: object |
2299 | properties: | 2385 | properties: |
2300 | allowed: | 2386 | allowed: |
2301 | type: boolean | 2387 | type: boolean |
2388 | allowedForCurrentIP: | ||
2389 | type: boolean | ||
2390 | requiresEmailVerification: | ||
2391 | type: boolean | ||
2302 | transcoding: | 2392 | transcoding: |
2303 | type: object | 2393 | type: object |
2304 | properties: | 2394 | properties: |
2395 | hls: | ||
2396 | type: object | ||
2397 | properties: | ||
2398 | enabled: | ||
2399 | type: boolean | ||
2305 | enabledResolutions: | 2400 | enabledResolutions: |
2306 | type: array | 2401 | type: array |
2307 | items: | 2402 | items: |
2308 | type: number | 2403 | type: number |
2404 | import: | ||
2405 | type: object | ||
2406 | properties: | ||
2407 | videos: | ||
2408 | type: object | ||
2409 | properties: | ||
2410 | http: | ||
2411 | type: object | ||
2412 | properties: | ||
2413 | enabled: | ||
2414 | type: boolean | ||
2415 | torrent: | ||
2416 | type: object | ||
2417 | properties: | ||
2418 | enabled: | ||
2419 | type: boolean | ||
2420 | autoBlacklist: | ||
2421 | type: object | ||
2422 | properties: | ||
2423 | videos: | ||
2424 | type: object | ||
2425 | properties: | ||
2426 | ofUsers: | ||
2427 | type: object | ||
2428 | properties: | ||
2429 | enabled: | ||
2430 | type: boolean | ||
2309 | avatar: | 2431 | avatar: |
2310 | type: object | 2432 | type: object |
2311 | properties: | 2433 | properties: |
@@ -2324,6 +2446,18 @@ components: | |||
2324 | video: | 2446 | video: |
2325 | type: object | 2447 | type: object |
2326 | properties: | 2448 | properties: |
2449 | image: | ||
2450 | type: object | ||
2451 | properties: | ||
2452 | extensions: | ||
2453 | type: array | ||
2454 | items: | ||
2455 | type: string | ||
2456 | size: | ||
2457 | type: object | ||
2458 | properties: | ||
2459 | max: | ||
2460 | type: number | ||
2327 | file: | 2461 | file: |
2328 | type: object | 2462 | type: object |
2329 | properties: | 2463 | properties: |
@@ -2331,6 +2465,202 @@ components: | |||
2331 | type: array | 2465 | type: array |
2332 | items: | 2466 | items: |
2333 | type: string | 2467 | type: string |
2468 | videoCaption: | ||
2469 | type: object | ||
2470 | properties: | ||
2471 | file: | ||
2472 | type: object | ||
2473 | properties: | ||
2474 | size: | ||
2475 | type: object | ||
2476 | properties: | ||
2477 | max: | ||
2478 | type: number | ||
2479 | extensions: | ||
2480 | type: array | ||
2481 | items: | ||
2482 | type: string | ||
2483 | user: | ||
2484 | type: object | ||
2485 | properties: | ||
2486 | videoQuota: | ||
2487 | type: number | ||
2488 | videoQuotaDaily: | ||
2489 | type: number | ||
2490 | trending: | ||
2491 | type: object | ||
2492 | properties: | ||
2493 | videos: | ||
2494 | type: object | ||
2495 | properties: | ||
2496 | intervalDays: | ||
2497 | type: number | ||
2498 | tracker: | ||
2499 | ype: object | ||
2500 | properties: | ||
2501 | enabled: | ||
2502 | type: boolean | ||
2503 | ServerConfigAbout: | ||
2504 | properties: | ||
2505 | instance: | ||
2506 | type: object | ||
2507 | properties: | ||
2508 | name: | ||
2509 | type: string | ||
2510 | shortDescription: | ||
2511 | type: string | ||
2512 | description: | ||
2513 | type: string | ||
2514 | terms: | ||
2515 | type: string | ||
2516 | ServerConfigCustom: | ||
2517 | properties: | ||
2518 | instance: | ||
2519 | type: object | ||
2520 | properties: | ||
2521 | name: | ||
2522 | type: string | ||
2523 | shortDescription: | ||
2524 | type: string | ||
2525 | description: | ||
2526 | type: string | ||
2527 | terms: | ||
2528 | type: string | ||
2529 | defaultClientRoute: | ||
2530 | type: string | ||
2531 | isNSFW: | ||
2532 | type: boolean | ||
2533 | defaultNSFWPolicy: | ||
2534 | type: string | ||
2535 | customizations: | ||
2536 | type: object | ||
2537 | properties: | ||
2538 | javascript: | ||
2539 | type: string | ||
2540 | css: | ||
2541 | type: string | ||
2542 | theme: | ||
2543 | type: object | ||
2544 | properties: | ||
2545 | default: | ||
2546 | type: string | ||
2547 | services: | ||
2548 | type: object | ||
2549 | properties: | ||
2550 | twitter: | ||
2551 | type: object | ||
2552 | properties: | ||
2553 | username: | ||
2554 | type: string | ||
2555 | whitelisted: | ||
2556 | type: boolean | ||
2557 | cache: | ||
2558 | type: object | ||
2559 | properties: | ||
2560 | previews: | ||
2561 | type: object | ||
2562 | properties: | ||
2563 | size: | ||
2564 | type: number | ||
2565 | captions: | ||
2566 | type: object | ||
2567 | properties: | ||
2568 | size: | ||
2569 | type: number | ||
2570 | signup: | ||
2571 | type: object | ||
2572 | properties: | ||
2573 | enabled: | ||
2574 | type: boolean | ||
2575 | limit: | ||
2576 | type: number | ||
2577 | requiresEmailVerification: | ||
2578 | type: boolean | ||
2579 | admin: | ||
2580 | type: object | ||
2581 | properties: | ||
2582 | email: | ||
2583 | type: string | ||
2584 | contactForm: | ||
2585 | type: object | ||
2586 | properties: | ||
2587 | enabled: | ||
2588 | type: boolean | ||
2589 | user: | ||
2590 | type: object | ||
2591 | properties: | ||
2592 | videoQuota: | ||
2593 | type: number | ||
2594 | videoQuotaDaily: | ||
2595 | type: number | ||
2596 | transcoding: | ||
2597 | type: object | ||
2598 | properties: | ||
2599 | enabled: | ||
2600 | type: boolean | ||
2601 | allowAdditionalExtensions: | ||
2602 | type: boolean | ||
2603 | allowAudioFiles: | ||
2604 | type: boolean | ||
2605 | threads: | ||
2606 | type: number | ||
2607 | resolutions: | ||
2608 | type: object | ||
2609 | properties: | ||
2610 | 240p: | ||
2611 | type: boolean | ||
2612 | 360p: | ||
2613 | type: boolean | ||
2614 | 480p: | ||
2615 | type: boolean | ||
2616 | 720p: | ||
2617 | type: boolean | ||
2618 | 1080p: | ||
2619 | type: boolean | ||
2620 | 2160p: | ||
2621 | type: boolean | ||
2622 | hls: | ||
2623 | type: object | ||
2624 | properties: | ||
2625 | enabled: | ||
2626 | type: boolean | ||
2627 | import: | ||
2628 | type: object | ||
2629 | properties: | ||
2630 | videos: | ||
2631 | type: object | ||
2632 | properties: | ||
2633 | http: | ||
2634 | type: object | ||
2635 | properties: | ||
2636 | enabled: | ||
2637 | type: boolean | ||
2638 | torrent: | ||
2639 | type: object | ||
2640 | properties: | ||
2641 | enabled: | ||
2642 | type: boolean | ||
2643 | autoBlacklist: | ||
2644 | type: object | ||
2645 | properties: | ||
2646 | videos: | ||
2647 | type: object | ||
2648 | properties: | ||
2649 | ofUsers: | ||
2650 | type: object | ||
2651 | properties: | ||
2652 | enabled: | ||
2653 | type: boolean | ||
2654 | followers: | ||
2655 | type: object | ||
2656 | properties: | ||
2657 | instance: | ||
2658 | type: object | ||
2659 | properties: | ||
2660 | enabled: | ||
2661 | type: boolean | ||
2662 | manualApproval: | ||
2663 | type: boolean | ||
2334 | Follow: | 2664 | Follow: |
2335 | properties: | 2665 | properties: |
2336 | id: | 2666 | id: |
diff --git a/support/doc/tools.md b/support/doc/tools.md index cf427ec84..dd2a03db7 100644 --- a/support/doc/tools.md +++ b/support/doc/tools.md | |||
@@ -11,6 +11,7 @@ | |||
11 | - [peertube-import-videos.js](#peertube-import-videosjs) | 11 | - [peertube-import-videos.js](#peertube-import-videosjs) |
12 | - [peertube-upload.js](#peertube-uploadjs) | 12 | - [peertube-upload.js](#peertube-uploadjs) |
13 | - [peertube-watch.js](#peertube-watchjs) | 13 | - [peertube-watch.js](#peertube-watchjs) |
14 | - [peertube-plugins.js](#peertube-pluginsjs) | ||
14 | - [Server tools](#server-tools) | 15 | - [Server tools](#server-tools) |
15 | - [parse-log](#parse-log) | 16 | - [parse-log](#parse-log) |
16 | - [create-transcoding-job.js](#create-transcoding-jobjs) | 17 | - [create-transcoding-job.js](#create-transcoding-jobjs) |
@@ -19,6 +20,7 @@ | |||
19 | - [optimize-old-videos.js](#optimize-old-videosjs) | 20 | - [optimize-old-videos.js](#optimize-old-videosjs) |
20 | - [update-host.js](#update-hostjs) | 21 | - [update-host.js](#update-hostjs) |
21 | - [reset-password.js](#reset-passwordjs) | 22 | - [reset-password.js](#reset-passwordjs) |
23 | - [plugin install/uninstall](#plugin-installuninstall) | ||
22 | - [REPL (Read Eval Print Loop)](#repl-read-eval-print-loop) | 24 | - [REPL (Read Eval Print Loop)](#repl-read-eval-print-loop) |
23 | - [.help](#help) | 25 | - [.help](#help) |
24 | - [Lodash example](#lodash-example) | 26 | - [Lodash example](#lodash-example) |
@@ -182,6 +184,22 @@ It provides support for different players: | |||
182 | - chromecast | 184 | - chromecast |
183 | 185 | ||
184 | 186 | ||
187 | #### peertube-plugins.js | ||
188 | |||
189 | Install/update/uninstall or list local or NPM PeerTube plugins: | ||
190 | |||
191 | ``` | ||
192 | $ cd ${CLONE} | ||
193 | $ node dist/server/tools/peertube-plugins.js --help | ||
194 | $ node dist/server/tools/peertube-plugins.js list --help | ||
195 | $ node dist/server/tools/peertube-plugins.js install --help | ||
196 | $ node dist/server/tools/peertube-plugins.js update --help | ||
197 | $ node dist/server/tools/peertube-plugins.js uninstall --help | ||
198 | |||
199 | $ node dist/server/tools/peertube-plugins.js install --path /my/plugin/path | ||
200 | $ node dist/server/tools/peertube-plugins.js install --npm-name peertube-theme-example | ||
201 | ``` | ||
202 | |||
185 | ## Server tools | 203 | ## Server tools |
186 | 204 | ||
187 | These scripts should be run on the server, in `peertube-latest` directory. | 205 | These scripts should be run on the server, in `peertube-latest` directory. |
@@ -262,22 +280,22 @@ $ sudo -u peertube NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production | |||
262 | The difference with `peertube plugins` CLI is that these scripts can be used even if PeerTube is not running. | 280 | The difference with `peertube plugins` CLI is that these scripts can be used even if PeerTube is not running. |
263 | If PeerTube is running, you need to restart it for the changes to take effect (whereas with `peertube plugins` CLI, plugins/themes are dynamically loaded on the server). | 281 | If PeerTube is running, you need to restart it for the changes to take effect (whereas with `peertube plugins` CLI, plugins/themes are dynamically loaded on the server). |
264 | 282 | ||
265 | To install a plugin or a theme from the disk: | 283 | To install/update a plugin or a theme from the disk: |
266 | 284 | ||
267 | ``` | 285 | ``` |
268 | $ sudo -u peertube NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production npm run npm run plugin:install -- --plugin-path /local/plugin/path | 286 | $ sudo -u peertube NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production npm run plugin:install -- --plugin-path /local/plugin/path |
269 | ``` | 287 | ``` |
270 | 288 | ||
271 | From NPM: | 289 | From NPM: |
272 | 290 | ||
273 | ``` | 291 | ``` |
274 | $ sudo -u peertube NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production npm run npm run plugin:install -- --npm-name peertube-plugin-myplugin | 292 | $ sudo -u peertube NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production npm run plugin:install -- --npm-name peertube-plugin-myplugin |
275 | ``` | 293 | ``` |
276 | 294 | ||
277 | To uninstall a plugin or a theme: | 295 | To uninstall a plugin or a theme: |
278 | 296 | ||
279 | ``` | 297 | ``` |
280 | $ sudo -u peertube NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production npm run npm run plugin:uninstall -- --npm-name peertube-plugin-myplugin | 298 | $ sudo -u peertube NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production npm run plugin:uninstall -- --npm-name peertube-plugin-myplugin |
281 | ``` | 299 | ``` |
282 | 300 | ||
283 | ### REPL ([Read Eval Print Loop](https://nodejs.org/docs/latest-v10.x/api/repl.html)) | 301 | ### REPL ([Read Eval Print Loop](https://nodejs.org/docs/latest-v10.x/api/repl.html)) |
diff --git a/tsconfig.json b/tsconfig.json index 4d2bdd6ba..f2985f82b 100644 --- a/tsconfig.json +++ b/tsconfig.json | |||
@@ -14,11 +14,15 @@ | |||
14 | "es2016", | 14 | "es2016", |
15 | "es2017" | 15 | "es2017" |
16 | ], | 16 | ], |
17 | "typeRoots": [ "node_modules/@types", "server/typings" ] | 17 | "typeRoots": [ "node_modules/@types", "server/typings" ], |
18 | "baseUrl": "./", | ||
19 | "paths": { | ||
20 | "@server/*": [ "server/*" ], | ||
21 | "@shared/*": [ "shared/*" ] | ||
22 | } | ||
18 | }, | 23 | }, |
19 | "exclude": [ | 24 | "exclude": [ |
20 | "server/tools/", | 25 | "server/tools/", |
21 | "client/node_modules", | ||
22 | "node_modules", | 26 | "node_modules", |
23 | "dist", | 27 | "dist", |
24 | "storage", | 28 | "storage", |
@@ -828,24 +828,6 @@ bindings@~1.3.0: | |||
828 | resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.3.1.tgz#21fc7c6d67c18516ec5aaa2815b145ff77b26ea5" | 828 | resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.3.1.tgz#21fc7c6d67c18516ec5aaa2815b145ff77b26ea5" |
829 | integrity sha512-i47mqjF9UbjxJhxGf+pZ6kSxrnI3wBLlnGI2ArWJ4r0VrvDS7ZYXkprq/pLaBWYq4GM0r4zdHY+NNRqEMU7uew== | 829 | integrity sha512-i47mqjF9UbjxJhxGf+pZ6kSxrnI3wBLlnGI2ArWJ4r0VrvDS7ZYXkprq/pLaBWYq4GM0r4zdHY+NNRqEMU7uew== |
830 | 830 | ||
831 | bitcore-lib@^0.13.7: | ||
832 | version "0.13.19" | ||
833 | resolved "https://registry.yarnpkg.com/bitcore-lib/-/bitcore-lib-0.13.19.tgz#48af1e9bda10067c1ab16263472b5add2000f3dc" | ||
834 | integrity sha1-SK8em9oQBnwasWJjRyta3SAA89w= | ||
835 | dependencies: | ||
836 | bn.js "=2.0.4" | ||
837 | bs58 "=2.0.0" | ||
838 | buffer-compare "=1.0.0" | ||
839 | elliptic "=3.0.3" | ||
840 | inherits "=2.0.1" | ||
841 | lodash "=3.10.1" | ||
842 | |||
843 | "bitcore-message@github:CoMakery/bitcore-message#dist": | ||
844 | version "1.0.2" | ||
845 | resolved "https://codeload.github.com/CoMakery/bitcore-message/tar.gz/8799cc327029c3d34fc725f05b2cf981363f6ebf" | ||
846 | dependencies: | ||
847 | bitcore-lib "^0.13.7" | ||
848 | |||
849 | bitfield@^2.0.0: | 831 | bitfield@^2.0.0: |
850 | version "2.0.0" | 832 | version "2.0.0" |
851 | resolved "https://registry.yarnpkg.com/bitfield/-/bitfield-2.0.0.tgz#fbe6767592fe5b4c87ecf1d04126294cc1bfa837" | 833 | resolved "https://registry.yarnpkg.com/bitfield/-/bitfield-2.0.0.tgz#fbe6767592fe5b4c87ecf1d04126294cc1bfa837" |
@@ -968,16 +950,6 @@ bluebird@^3.0.5, bluebird@^3.5.0: | |||
968 | resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.5.tgz#a8d0afd73251effbbd5fe384a77d73003c17a71f" | 950 | resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.5.tgz#a8d0afd73251effbbd5fe384a77d73003c17a71f" |
969 | integrity sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w== | 951 | integrity sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w== |
970 | 952 | ||
971 | bn.js@=2.0.4: | ||
972 | version "2.0.4" | ||
973 | resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-2.0.4.tgz#220a7cd677f7f1bfa93627ff4193776fe7819480" | ||
974 | integrity sha1-Igp81nf38b+pNif/QZN3b+eBlIA= | ||
975 | |||
976 | bn.js@^2.0.0: | ||
977 | version "2.2.0" | ||
978 | resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-2.2.0.tgz#12162bc2ae71fc40a5626c33438f3a875cd37625" | ||
979 | integrity sha1-EhYrwq5x/EClYmwzQ486h1zTdiU= | ||
980 | |||
981 | bn.js@^4.4.0: | 953 | bn.js@^4.4.0: |
982 | version "4.11.8" | 954 | version "4.11.8" |
983 | resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" | 955 | resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" |
@@ -1043,11 +1015,6 @@ braces@^3.0.1: | |||
1043 | dependencies: | 1015 | dependencies: |
1044 | fill-range "^7.0.1" | 1016 | fill-range "^7.0.1" |
1045 | 1017 | ||
1046 | brorand@^1.0.1: | ||
1047 | version "1.1.0" | ||
1048 | resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" | ||
1049 | integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= | ||
1050 | |||
1051 | browser-stdout@1.3.1: | 1018 | browser-stdout@1.3.1: |
1052 | version "1.3.1" | 1019 | version "1.3.1" |
1053 | resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" | 1020 | resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" |
@@ -1058,11 +1025,6 @@ browserify-package-json@^1.0.0: | |||
1058 | resolved "https://registry.yarnpkg.com/browserify-package-json/-/browserify-package-json-1.0.1.tgz#98dde8aa5c561fd6d3fe49bbaa102b74b396fdea" | 1025 | resolved "https://registry.yarnpkg.com/browserify-package-json/-/browserify-package-json-1.0.1.tgz#98dde8aa5c561fd6d3fe49bbaa102b74b396fdea" |
1059 | integrity sha1-mN3oqlxWH9bT/km7qhArdLOW/eo= | 1026 | integrity sha1-mN3oqlxWH9bT/km7qhArdLOW/eo= |
1060 | 1027 | ||
1061 | bs58@=2.0.0: | ||
1062 | version "2.0.0" | ||
1063 | resolved "https://registry.yarnpkg.com/bs58/-/bs58-2.0.0.tgz#72b713bed223a0ac518bbda0e3ce3f4817f39eb5" | ||
1064 | integrity sha1-crcTvtIjoKxRi72g484/SBfznrU= | ||
1065 | |||
1066 | buffer-alloc-unsafe@^1.1.0: | 1028 | buffer-alloc-unsafe@^1.1.0: |
1067 | version "1.1.0" | 1029 | version "1.1.0" |
1068 | resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0" | 1030 | resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0" |
@@ -1076,16 +1038,6 @@ buffer-alloc@^1.1.0, buffer-alloc@^1.2.0: | |||
1076 | buffer-alloc-unsafe "^1.1.0" | 1038 | buffer-alloc-unsafe "^1.1.0" |
1077 | buffer-fill "^1.0.0" | 1039 | buffer-fill "^1.0.0" |
1078 | 1040 | ||
1079 | buffer-compare@=1.0.0: | ||
1080 | version "1.0.0" | ||
1081 | resolved "https://registry.yarnpkg.com/buffer-compare/-/buffer-compare-1.0.0.tgz#acaa7a966e98eee9fae14b31c39a5f158fb3c4a2" | ||
1082 | integrity sha1-rKp6lm6Y7un64Usxw5pfFY+zxKI= | ||
1083 | |||
1084 | buffer-equal-constant-time@1.0.1: | ||
1085 | version "1.0.1" | ||
1086 | resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" | ||
1087 | integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk= | ||
1088 | |||
1089 | buffer-equals@^1.0.3, buffer-equals@^1.0.4: | 1041 | buffer-equals@^1.0.3, buffer-equals@^1.0.4: |
1090 | version "1.0.4" | 1042 | version "1.0.4" |
1091 | resolved "https://registry.yarnpkg.com/buffer-equals/-/buffer-equals-1.0.4.tgz#0353b54fd07fd9564170671ae6f66b9cf10d27f5" | 1043 | resolved "https://registry.yarnpkg.com/buffer-equals/-/buffer-equals-1.0.4.tgz#0353b54fd07fd9564170671ae6f66b9cf10d27f5" |
@@ -2096,13 +2048,6 @@ ecc-jsbn@~0.1.1: | |||
2096 | jsbn "~0.1.0" | 2048 | jsbn "~0.1.0" |
2097 | safer-buffer "^2.1.0" | 2049 | safer-buffer "^2.1.0" |
2098 | 2050 | ||
2099 | ecdsa-sig-formatter@1.0.11: | ||
2100 | version "1.0.11" | ||
2101 | resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" | ||
2102 | integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== | ||
2103 | dependencies: | ||
2104 | safe-buffer "^5.0.1" | ||
2105 | |||
2106 | ee-first@1.1.1: | 2051 | ee-first@1.1.1: |
2107 | version "1.1.1" | 2052 | version "1.1.1" |
2108 | resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" | 2053 | resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" |
@@ -2113,16 +2058,6 @@ elegant-spinner@^1.0.1: | |||
2113 | resolved "https://registry.yarnpkg.com/elegant-spinner/-/elegant-spinner-1.0.1.tgz#db043521c95d7e303fd8f345bedc3349cfb0729e" | 2058 | resolved "https://registry.yarnpkg.com/elegant-spinner/-/elegant-spinner-1.0.1.tgz#db043521c95d7e303fd8f345bedc3349cfb0729e" |
2114 | integrity sha1-2wQ1IcldfjA/2PNFvtwzSc+wcp4= | 2059 | integrity sha1-2wQ1IcldfjA/2PNFvtwzSc+wcp4= |
2115 | 2060 | ||
2116 | elliptic@=3.0.3: | ||
2117 | version "3.0.3" | ||
2118 | resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-3.0.3.tgz#865c9b420bfbe55006b9f969f97a0d2c44966595" | ||
2119 | integrity sha1-hlybQgv75VAGuflp+XoNLESWZZU= | ||
2120 | dependencies: | ||
2121 | bn.js "^2.0.0" | ||
2122 | brorand "^1.0.1" | ||
2123 | hash.js "^1.0.0" | ||
2124 | inherits "^2.0.1" | ||
2125 | |||
2126 | emoji-regex@^7.0.1: | 2061 | emoji-regex@^7.0.1: |
2127 | version "7.0.3" | 2062 | version "7.0.3" |
2128 | resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" | 2063 | resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" |
@@ -3250,14 +3185,6 @@ has@^1.0.1, has@^1.0.3: | |||
3250 | dependencies: | 3185 | dependencies: |
3251 | function-bind "^1.1.1" | 3186 | function-bind "^1.1.1" |
3252 | 3187 | ||
3253 | hash.js@^1.0.0: | ||
3254 | version "1.1.7" | ||
3255 | resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" | ||
3256 | integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== | ||
3257 | dependencies: | ||
3258 | inherits "^2.0.3" | ||
3259 | minimalistic-assert "^1.0.1" | ||
3260 | |||
3261 | hashish@~0.0.4: | 3188 | hashish@~0.0.4: |
3262 | version "0.0.4" | 3189 | version "0.0.4" |
3263 | resolved "https://registry.yarnpkg.com/hashish/-/hashish-0.0.4.tgz#6d60bc6ffaf711b6afd60e426d077988014e6554" | 3190 | resolved "https://registry.yarnpkg.com/hashish/-/hashish-0.0.4.tgz#6d60bc6ffaf711b6afd60e426d077988014e6554" |
@@ -3481,11 +3408,6 @@ inherits@2.0.3: | |||
3481 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" | 3408 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" |
3482 | integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= | 3409 | integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= |
3483 | 3410 | ||
3484 | inherits@=2.0.1: | ||
3485 | version "2.0.1" | ||
3486 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" | ||
3487 | integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE= | ||
3488 | |||
3489 | ini@^1.3.4, ini@~1.3.0: | 3411 | ini@^1.3.4, ini@~1.3.0: |
3490 | version "1.3.5" | 3412 | version "1.3.5" |
3491 | resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" | 3413 | resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" |
@@ -4028,25 +3950,6 @@ jsonify@~0.0.0: | |||
4028 | resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" | 3950 | resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" |
4029 | integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM= | 3951 | integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM= |
4030 | 3952 | ||
4031 | "jsonld-signatures@https://github.com/Chocobozzz/jsonld-signatures#rsa2017": | ||
4032 | version "1.2.2-2" | ||
4033 | resolved "https://github.com/Chocobozzz/jsonld-signatures#77660963e722eb4541d2d255f9d9d4216329665f" | ||
4034 | dependencies: | ||
4035 | bitcore-message "github:CoMakery/bitcore-message#dist" | ||
4036 | jsonld "^0.5.12" | ||
4037 | jws "^3.1.4" | ||
4038 | node-forge "^0.7.1" | ||
4039 | |||
4040 | jsonld@^0.5.12: | ||
4041 | version "0.5.21" | ||
4042 | resolved "https://registry.yarnpkg.com/jsonld/-/jsonld-0.5.21.tgz#4d5b78d717eb92bcd1ac9d88e34efad95370c0bf" | ||
4043 | integrity sha512-1dQhaw1Eb3p7Cz5ECE2DNPwLvTmK+f6D45hACBdonJaFKP1bN9zlKLZWbPZQeZtduAc/LNv10J4ML0IiTBVahw== | ||
4044 | dependencies: | ||
4045 | rdf-canonize "^0.2.1" | ||
4046 | request "^2.83.0" | ||
4047 | semver "^5.5.0" | ||
4048 | xmldom "0.1.19" | ||
4049 | |||
4050 | jsonld@~1.1.0: | 3953 | jsonld@~1.1.0: |
4051 | version "1.1.0" | 3954 | version "1.1.0" |
4052 | resolved "https://registry.yarnpkg.com/jsonld/-/jsonld-1.1.0.tgz#afcb168c44557a7bddead4d4513c3cbcae3bc5b9" | 3955 | resolved "https://registry.yarnpkg.com/jsonld/-/jsonld-1.1.0.tgz#afcb168c44557a7bddead4d4513c3cbcae3bc5b9" |
@@ -4082,23 +3985,6 @@ junk@^3.1.0: | |||
4082 | resolved "https://registry.yarnpkg.com/junk/-/junk-3.1.0.tgz#31499098d902b7e98c5d9b9c80f43457a88abfa1" | 3985 | resolved "https://registry.yarnpkg.com/junk/-/junk-3.1.0.tgz#31499098d902b7e98c5d9b9c80f43457a88abfa1" |
4083 | integrity sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ== | 3986 | integrity sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ== |
4084 | 3987 | ||
4085 | jwa@^1.4.1: | ||
4086 | version "1.4.1" | ||
4087 | resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" | ||
4088 | integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== | ||
4089 | dependencies: | ||
4090 | buffer-equal-constant-time "1.0.1" | ||
4091 | ecdsa-sig-formatter "1.0.11" | ||
4092 | safe-buffer "^5.0.1" | ||
4093 | |||
4094 | jws@^3.1.4: | ||
4095 | version "3.2.2" | ||
4096 | resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" | ||
4097 | integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== | ||
4098 | dependencies: | ||
4099 | jwa "^1.4.1" | ||
4100 | safe-buffer "^5.0.1" | ||
4101 | |||
4102 | k-bucket@^4.0.0: | 3988 | k-bucket@^4.0.0: |
4103 | version "4.0.1" | 3989 | version "4.0.1" |
4104 | resolved "https://registry.yarnpkg.com/k-bucket/-/k-bucket-4.0.1.tgz#3fc2e5693f0b7bff90d7b6b476edd6087955d542" | 3990 | resolved "https://registry.yarnpkg.com/k-bucket/-/k-bucket-4.0.1.tgz#3fc2e5693f0b7bff90d7b6b476edd6087955d542" |
@@ -4335,11 +4221,6 @@ lodash@4.17.4: | |||
4335 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" | 4221 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" |
4336 | integrity sha1-eCA6TRwyiuHYbcpkYONptX9AVa4= | 4222 | integrity sha1-eCA6TRwyiuHYbcpkYONptX9AVa4= |
4337 | 4223 | ||
4338 | lodash@=3.10.1: | ||
4339 | version "3.10.1" | ||
4340 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6" | ||
4341 | integrity sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y= | ||
4342 | |||
4343 | lodash@^4.0.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.3.0, lodash@~4.17.10: | 4224 | lodash@^4.0.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.3.0, lodash@~4.17.10: |
4344 | version "4.17.15" | 4225 | version "4.17.15" |
4345 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" | 4226 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" |
@@ -4638,11 +4519,6 @@ mimic-response@^1.0.0: | |||
4638 | resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" | 4519 | resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" |
4639 | integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== | 4520 | integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== |
4640 | 4521 | ||
4641 | minimalistic-assert@^1.0.1: | ||
4642 | version "1.0.1" | ||
4643 | resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" | ||
4644 | integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== | ||
4645 | |||
4646 | minimatch@3.0.4, minimatch@^3.0.4, minimatch@~3.0.2: | 4522 | minimatch@3.0.4, minimatch@^3.0.4, minimatch@~3.0.2: |
4647 | version "3.0.4" | 4523 | version "3.0.4" |
4648 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" | 4524 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" |
@@ -4734,6 +4610,11 @@ mocha@^6.0.0: | |||
4734 | yargs-parser "13.0.0" | 4610 | yargs-parser "13.0.0" |
4735 | yargs-unparser "1.5.0" | 4611 | yargs-unparser "1.5.0" |
4736 | 4612 | ||
4613 | module-alias@^2.2.1: | ||
4614 | version "2.2.1" | ||
4615 | resolved "https://registry.yarnpkg.com/module-alias/-/module-alias-2.2.1.tgz#553aea9dc7f99cd45fd75e34a574960dc46550da" | ||
4616 | integrity sha512-LTez0Eo+YtfUhgzhu/LqxkUzOpD+k5C0wXBLun0L1qE2BhHf6l09dqam8e7BnoMYA6mAlP0vSsGFQ8QHhGN/aQ== | ||
4617 | |||
4737 | moment-timezone@^0.5.21, moment-timezone@^0.5.25: | 4618 | moment-timezone@^0.5.21, moment-timezone@^0.5.25: |
4738 | version "0.5.26" | 4619 | version "0.5.26" |
4739 | resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.26.tgz#c0267ca09ae84631aa3dc33f65bedbe6e8e0d772" | 4620 | resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.26.tgz#c0267ca09ae84631aa3dc33f65bedbe6e8e0d772" |