aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--client/src/app/+admin/admin-routing.module.ts4
-rw-r--r--client/src/app/+admin/admin.module.ts23
-rw-r--r--client/src/app/+admin/follows/followers-list/followers-list.component.html16
-rw-r--r--client/src/app/+admin/follows/followers-list/followers-list.component.scss (renamed from client/src/app/+admin/friends/friend-list/friend-list.component.scss)0
-rw-r--r--client/src/app/+admin/follows/followers-list/followers-list.component.ts41
-rw-r--r--client/src/app/+admin/follows/followers-list/index.ts1
-rw-r--r--client/src/app/+admin/follows/following-add/following-add.component.html (renamed from client/src/app/+admin/friends/friend-add/friend-add.component.html)8
-rw-r--r--client/src/app/+admin/follows/following-add/following-add.component.scss (renamed from client/src/app/+admin/friends/friend-add/friend-add.component.scss)0
-rw-r--r--client/src/app/+admin/follows/following-add/following-add.component.ts (renamed from client/src/app/+admin/friends/friend-add/friend-add.component.ts)20
-rw-r--r--client/src/app/+admin/follows/following-add/index.ts1
-rw-r--r--client/src/app/+admin/follows/following-list/following-list.component.html16
-rw-r--r--client/src/app/+admin/follows/following-list/following-list.component.ts40
-rw-r--r--client/src/app/+admin/follows/following-list/index.ts1
-rw-r--r--client/src/app/+admin/follows/follows.component.html11
-rw-r--r--client/src/app/+admin/follows/follows.component.scss21
-rw-r--r--client/src/app/+admin/follows/follows.component.ts43
-rw-r--r--client/src/app/+admin/follows/follows.routes.ts53
-rw-r--r--client/src/app/+admin/follows/index.ts6
-rw-r--r--client/src/app/+admin/follows/shared/follow.service.ts (renamed from client/src/app/+admin/friends/shared/friend.service.ts)29
-rw-r--r--client/src/app/+admin/follows/shared/index.ts1
-rw-r--r--client/src/app/+admin/friends/friend-add/index.ts1
-rw-r--r--client/src/app/+admin/friends/friend-list/friend-list.component.html29
-rw-r--r--client/src/app/+admin/friends/friend-list/friend-list.component.ts87
-rw-r--r--client/src/app/+admin/friends/friend-list/index.ts1
-rw-r--r--client/src/app/+admin/friends/friends.component.ts7
-rw-r--r--client/src/app/+admin/friends/friends.routes.ts43
-rw-r--r--client/src/app/+admin/friends/index.ts5
-rw-r--r--client/src/app/+admin/friends/shared/index.ts1
-rw-r--r--client/src/app/core/menu/menu-admin.component.html4
-rw-r--r--client/src/app/core/menu/menu-admin.component.ts4
-rw-r--r--client/src/app/core/menu/menu.component.ts4
-rwxr-xr-xscripts/update-host.ts2
-rw-r--r--server/controllers/activitypub/client.ts4
-rw-r--r--server/controllers/activitypub/index.ts5
-rw-r--r--server/controllers/api/application/follows.ts (renamed from server/controllers/api/pods.ts)50
-rw-r--r--server/controllers/api/application/index.ts12
-rw-r--r--server/controllers/api/index.ts4
-rw-r--r--server/controllers/webfinger.ts2
-rw-r--r--server/lib/activitypub/send-request.ts2
-rw-r--r--server/models/account/account-follow-interface.ts16
-rw-r--r--server/models/account/account-follow.ts130
-rw-r--r--server/models/account/account-interface.ts8
-rw-r--r--server/models/account/account.ts132
-rw-r--r--shared/models/users/user-right.enum.ts4
44 files changed, 495 insertions, 397 deletions
diff --git a/client/src/app/+admin/admin-routing.module.ts b/client/src/app/+admin/admin-routing.module.ts
index 88f44a811..cd8b9bdef 100644
--- a/client/src/app/+admin/admin-routing.module.ts
+++ b/client/src/app/+admin/admin-routing.module.ts
@@ -4,7 +4,7 @@ import { RouterModule, Routes } from '@angular/router'
4import { MetaGuard } from '@ngx-meta/core' 4import { MetaGuard } from '@ngx-meta/core'
5 5
6import { AdminComponent } from './admin.component' 6import { AdminComponent } from './admin.component'
7import { FriendsRoutes } from './friends' 7import { FollowsRoutes } from './follows'
8import { UsersRoutes } from './users' 8import { UsersRoutes } from './users'
9import { VideoAbusesRoutes } from './video-abuses' 9import { VideoAbusesRoutes } from './video-abuses'
10import { VideoBlacklistRoutes } from './video-blacklist' 10import { VideoBlacklistRoutes } from './video-blacklist'
@@ -21,7 +21,7 @@ const adminRoutes: Routes = [
21 redirectTo: 'users', 21 redirectTo: 'users',
22 pathMatch: 'full' 22 pathMatch: 'full'
23 }, 23 },
24 ...FriendsRoutes, 24 ...FollowsRoutes,
25 ...UsersRoutes, 25 ...UsersRoutes,
26 ...VideoAbusesRoutes, 26 ...VideoAbusesRoutes,
27 ...VideoBlacklistRoutes 27 ...VideoBlacklistRoutes
diff --git a/client/src/app/+admin/admin.module.ts b/client/src/app/+admin/admin.module.ts
index 32f6c42a6..3c6b7a793 100644
--- a/client/src/app/+admin/admin.module.ts
+++ b/client/src/app/+admin/admin.module.ts
@@ -1,25 +1,28 @@
1import { NgModule } from '@angular/core' 1import { NgModule } from '@angular/core'
2 2import { TabsModule } from 'ngx-bootstrap/tabs'
3import { AdminComponent } from './admin.component' 3import { SharedModule } from '../shared'
4import { AdminRoutingModule } from './admin-routing.module' 4import { AdminRoutingModule } from './admin-routing.module'
5import { FriendsComponent, FriendAddComponent, FriendListComponent, FriendService } from './friends' 5import { AdminComponent } from './admin.component'
6import { UsersComponent, UserAddComponent, UserUpdateComponent, UserListComponent, UserService } from './users' 6import { FollowersListComponent, FollowingAddComponent, FollowsComponent, FollowService } from './follows'
7import { VideoAbusesComponent, VideoAbuseListComponent } from './video-abuses' 7import { FollowingListComponent } from './follows/following-list/following-list.component'
8import { UserAddComponent, UserListComponent, UsersComponent, UserService, UserUpdateComponent } from './users'
9import { VideoAbuseListComponent, VideoAbusesComponent } from './video-abuses'
8import { VideoBlacklistComponent, VideoBlacklistListComponent } from './video-blacklist' 10import { VideoBlacklistComponent, VideoBlacklistListComponent } from './video-blacklist'
9import { SharedModule } from '../shared'
10 11
11@NgModule({ 12@NgModule({
12 imports: [ 13 imports: [
13 AdminRoutingModule, 14 AdminRoutingModule,
15 TabsModule.forRoot(),
14 SharedModule 16 SharedModule
15 ], 17 ],
16 18
17 declarations: [ 19 declarations: [
18 AdminComponent, 20 AdminComponent,
19 21
20 FriendsComponent, 22 FollowsComponent,
21 FriendAddComponent, 23 FollowingAddComponent,
22 FriendListComponent, 24 FollowersListComponent,
25 FollowingListComponent,
23 26
24 UsersComponent, 27 UsersComponent,
25 UserAddComponent, 28 UserAddComponent,
@@ -38,7 +41,7 @@ import { SharedModule } from '../shared'
38 ], 41 ],
39 42
40 providers: [ 43 providers: [
41 FriendService, 44 FollowService,
42 UserService 45 UserService
43 ] 46 ]
44}) 47})
diff --git a/client/src/app/+admin/follows/followers-list/followers-list.component.html b/client/src/app/+admin/follows/followers-list/followers-list.component.html
new file mode 100644
index 000000000..24d75d2b3
--- /dev/null
+++ b/client/src/app/+admin/follows/followers-list/followers-list.component.html
@@ -0,0 +1,16 @@
1<div class="row">
2 <div class="content-padding">
3 <h3>Followers list</h3>
4
5 <p-dataTable
6 [value]="followers" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage"
7 sortField="createdAt" (onLazyLoad)="loadLazy($event)"
8 >
9 <p-column field="id" header="ID"></p-column>
10 <p-column field="host" header="Host"></p-column>
11 <p-column field="email" header="Email"></p-column>
12 <p-column field="score" header="Score"></p-column>
13 <p-column field="createdAt" header="Created date" [sortable]="true"></p-column>
14 </p-dataTable>
15 </div>
16</div>
diff --git a/client/src/app/+admin/friends/friend-list/friend-list.component.scss b/client/src/app/+admin/follows/followers-list/followers-list.component.scss
index 0a0f621c6..0a0f621c6 100644
--- a/client/src/app/+admin/friends/friend-list/friend-list.component.scss
+++ b/client/src/app/+admin/follows/followers-list/followers-list.component.scss
diff --git a/client/src/app/+admin/follows/followers-list/followers-list.component.ts b/client/src/app/+admin/follows/followers-list/followers-list.component.ts
new file mode 100644
index 000000000..208a0c648
--- /dev/null
+++ b/client/src/app/+admin/follows/followers-list/followers-list.component.ts
@@ -0,0 +1,41 @@
1import { Component, OnInit } from '@angular/core'
2
3import { NotificationsService } from 'angular2-notifications'
4import { SortMeta } from 'primeng/primeng'
5
6import { ConfirmService } from '../../../core'
7import { RestTable, RestPagination } from '../../../shared'
8import { Pod } from '../../../../../../shared'
9import { FollowService } from '../shared'
10
11@Component({
12 selector: 'my-followers-list',
13 templateUrl: './followers-list.component.html',
14 styleUrls: [ './followers-list.component.scss' ]
15})
16export class FollowersListComponent extends RestTable {
17 followers: Pod[] = []
18 totalRecords = 0
19 rowsPerPage = 10
20 sort: SortMeta = { field: 'createdAt', order: 1 }
21 pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
22
23 constructor (
24 private notificationsService: NotificationsService,
25 private followService: FollowService
26 ) {
27 super()
28 }
29
30 protected loadData () {
31 this.followService.getFollowers(this.pagination, this.sort)
32 .subscribe(
33 resultList => {
34 this.followers = resultList.data
35 this.totalRecords = resultList.total
36 },
37
38 err => this.notificationsService.error('Error', err.message)
39 )
40 }
41}
diff --git a/client/src/app/+admin/follows/followers-list/index.ts b/client/src/app/+admin/follows/followers-list/index.ts
new file mode 100644
index 000000000..15390cfbe
--- /dev/null
+++ b/client/src/app/+admin/follows/followers-list/index.ts
@@ -0,0 +1 @@
export * from './followers-list.component'
diff --git a/client/src/app/+admin/friends/friend-add/friend-add.component.html b/client/src/app/+admin/follows/following-add/following-add.component.html
index 81d8291cd..111f6a8de 100644
--- a/client/src/app/+admin/friends/friend-add/friend-add.component.html
+++ b/client/src/app/+admin/follows/following-add/following-add.component.html
@@ -1,11 +1,11 @@
1<div class="row"> 1<div class="row">
2 <div class="content-padding"> 2 <div class="content-padding">
3 3
4 <h3>Make friends</h3> 4 <h3>Add following</h3>
5 5
6 <div *ngIf="error" class="alert alert-danger">{{ error }}</div> 6 <div *ngIf="error" class="alert alert-danger">{{ error }}</div>
7 7
8 <form (ngSubmit)="makeFriends()" [formGroup]="form"> 8 <form (ngSubmit)="addFollowing()" [formGroup]="form">
9 <div class="form-group" *ngFor="let host of hosts; let id = index; trackBy:customTrackBy"> 9 <div class="form-group" *ngFor="let host of hosts; let id = index; trackBy:customTrackBy">
10 <label [for]="'host-' + id">Host (so without "http://")</label> 10 <label [for]="'host-' + id">Host (so without "http://")</label>
11 11
@@ -26,10 +26,10 @@
26 </div> 26 </div>
27 27
28 <div *ngIf="canMakeFriends() === false" class="alert alert-warning"> 28 <div *ngIf="canMakeFriends() === false" class="alert alert-warning">
29 It seems that you are not on a HTTPS pod. Your webserver need to have TLS activated in order to make friends. 29 It seems that you are not on a HTTPS pod. Your webserver need to have TLS activated in order to follow servers.
30 </div> 30 </div>
31 31
32 <input type="submit" value="Make friends" class="btn btn-default" [disabled]="!isFormValid()"> 32 <input type="submit" value="Add following" class="btn btn-default" [disabled]="!isFormValid()">
33 </form> 33 </form>
34 </div> 34 </div>
35</div> 35</div>
diff --git a/client/src/app/+admin/friends/friend-add/friend-add.component.scss b/client/src/app/+admin/follows/following-add/following-add.component.scss
index 5fde51636..5fde51636 100644
--- a/client/src/app/+admin/friends/friend-add/friend-add.component.scss
+++ b/client/src/app/+admin/follows/following-add/following-add.component.scss
diff --git a/client/src/app/+admin/friends/friend-add/friend-add.component.ts b/client/src/app/+admin/follows/following-add/following-add.component.ts
index 29ed23e0c..d95d6afa9 100644
--- a/client/src/app/+admin/friends/friend-add/friend-add.component.ts
+++ b/client/src/app/+admin/follows/following-add/following-add.component.ts
@@ -6,14 +6,14 @@ import { NotificationsService } from 'angular2-notifications'
6 6
7import { ConfirmService } from '../../../core' 7import { ConfirmService } from '../../../core'
8import { validateHost } from '../../../shared' 8import { validateHost } from '../../../shared'
9import { FriendService } from '../shared' 9import { FollowService } from '../shared'
10 10
11@Component({ 11@Component({
12 selector: 'my-friend-add', 12 selector: 'my-following-add',
13 templateUrl: './friend-add.component.html', 13 templateUrl: './following-add.component.html',
14 styleUrls: [ './friend-add.component.scss' ] 14 styleUrls: [ './following-add.component.scss' ]
15}) 15})
16export class FriendAddComponent implements OnInit { 16export class FollowingAddComponent implements OnInit {
17 form: FormGroup 17 form: FormGroup
18 hosts: string[] = [ ] 18 hosts: string[] = [ ]
19 error: string = null 19 error: string = null
@@ -22,7 +22,7 @@ export class FriendAddComponent implements OnInit {
22 private router: Router, 22 private router: Router,
23 private notificationsService: NotificationsService, 23 private notificationsService: NotificationsService,
24 private confirmService: ConfirmService, 24 private confirmService: ConfirmService,
25 private friendService: FriendService 25 private followService: FollowService
26 ) {} 26 ) {}
27 27
28 ngOnInit () { 28 ngOnInit () {
@@ -72,7 +72,7 @@ export class FriendAddComponent implements OnInit {
72 this.hosts.splice(index, 1) 72 this.hosts.splice(index, 1)
73 } 73 }
74 74
75 makeFriends () { 75 addFollowing () {
76 this.error = '' 76 this.error = ''
77 77
78 const notEmptyHosts = this.getNotEmptyHosts() 78 const notEmptyHosts = this.getNotEmptyHosts()
@@ -87,13 +87,13 @@ export class FriendAddComponent implements OnInit {
87 } 87 }
88 88
89 const confirmMessage = 'Are you sure to make friends with:<br /> - ' + notEmptyHosts.join('<br /> - ') 89 const confirmMessage = 'Are you sure to make friends with:<br /> - ' + notEmptyHosts.join('<br /> - ')
90 this.confirmService.confirm(confirmMessage, 'Make friends').subscribe( 90 this.confirmService.confirm(confirmMessage, 'Follow new server(s)').subscribe(
91 res => { 91 res => {
92 if (res === false) return 92 if (res === false) return
93 93
94 this.friendService.follow(notEmptyHosts).subscribe( 94 this.followService.follow(notEmptyHosts).subscribe(
95 status => { 95 status => {
96 this.notificationsService.success('Success', 'Make friends request sent!') 96 this.notificationsService.success('Success', 'Follow request(s) sent!')
97 // Wait requests between pods 97 // Wait requests between pods
98 setTimeout(() => this.router.navigate([ '/admin/friends/list' ]), 1000) 98 setTimeout(() => this.router.navigate([ '/admin/friends/list' ]), 1000)
99 }, 99 },
diff --git a/client/src/app/+admin/follows/following-add/index.ts b/client/src/app/+admin/follows/following-add/index.ts
new file mode 100644
index 000000000..1b1897ffa
--- /dev/null
+++ b/client/src/app/+admin/follows/following-add/index.ts
@@ -0,0 +1 @@
export * from './following-add.component'
diff --git a/client/src/app/+admin/follows/following-list/following-list.component.html b/client/src/app/+admin/follows/following-list/following-list.component.html
new file mode 100644
index 000000000..fbcebfaa7
--- /dev/null
+++ b/client/src/app/+admin/follows/following-list/following-list.component.html
@@ -0,0 +1,16 @@
1<div class="row">
2 <div class="content-padding">
3 <h3>Following list</h3>
4
5 <p-dataTable
6 [value]="following" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage"
7 sortField="createdAt" (onLazyLoad)="loadLazy($event)"
8 >
9 <p-column field="id" header="ID"></p-column>
10 <p-column field="host" header="Host"></p-column>
11 <p-column field="email" header="Email"></p-column>
12 <p-column field="score" header="Score"></p-column>
13 <p-column field="createdAt" header="Created date" [sortable]="true"></p-column>
14 </p-dataTable>
15 </div>
16</div>
diff --git a/client/src/app/+admin/follows/following-list/following-list.component.ts b/client/src/app/+admin/follows/following-list/following-list.component.ts
new file mode 100644
index 000000000..7d2c5084b
--- /dev/null
+++ b/client/src/app/+admin/follows/following-list/following-list.component.ts
@@ -0,0 +1,40 @@
1import { Component, OnInit } from '@angular/core'
2
3import { NotificationsService } from 'angular2-notifications'
4import { SortMeta } from 'primeng/primeng'
5
6import { ConfirmService } from '../../../core'
7import { RestTable, RestPagination } from '../../../shared'
8import { Pod } from '../../../../../../shared'
9import { FollowService } from '../shared'
10
11@Component({
12 selector: 'my-followers-list',
13 templateUrl: './following-list.component.html'
14})
15export class FollowingListComponent extends RestTable {
16 following: Pod[] = []
17 totalRecords = 0
18 rowsPerPage = 10
19 sort: SortMeta = { field: 'createdAt', order: 1 }
20 pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
21
22 constructor (
23 private notificationsService: NotificationsService,
24 private followService: FollowService
25 ) {
26 super()
27 }
28
29 protected loadData () {
30 this.followService.getFollowing(this.pagination, this.sort)
31 .subscribe(
32 resultList => {
33 this.following = resultList.data
34 this.totalRecords = resultList.total
35 },
36
37 err => this.notificationsService.error('Error', err.message)
38 )
39 }
40}
diff --git a/client/src/app/+admin/follows/following-list/index.ts b/client/src/app/+admin/follows/following-list/index.ts
new file mode 100644
index 000000000..a70d46a7e
--- /dev/null
+++ b/client/src/app/+admin/follows/following-list/index.ts
@@ -0,0 +1 @@
export * from './following-list.component'
diff --git a/client/src/app/+admin/follows/follows.component.html b/client/src/app/+admin/follows/follows.component.html
new file mode 100644
index 000000000..b67bc9736
--- /dev/null
+++ b/client/src/app/+admin/follows/follows.component.html
@@ -0,0 +1,11 @@
1<div class="follows-menu">
2 <tabset #followsMenuTabs>
3 <tab *ngFor="let link of links">
4 <ng-template tabHeading>
5 <a class="tab-link" [routerLink]="link.path">{{ link.title }}</a>
6 </ng-template>
7 </tab>
8 </tabset>
9</div>
10
11<router-outlet></router-outlet>
diff --git a/client/src/app/+admin/follows/follows.component.scss b/client/src/app/+admin/follows/follows.component.scss
new file mode 100644
index 000000000..d8ab41975
--- /dev/null
+++ b/client/src/app/+admin/follows/follows.component.scss
@@ -0,0 +1,21 @@
1.follows-menu {
2 margin-top: 20px;
3}
4
5tabset /deep/ {
6 .nav-link {
7 padding: 0;
8 }
9
10 .tab-link {
11 display: block;
12 text-align: center;
13 height: 40px;
14 width: 120px;
15 line-height: 40px;
16
17 &:hover, &:active, &:focus {
18 text-decoration: none !important;
19 }
20 }
21}
diff --git a/client/src/app/+admin/follows/follows.component.ts b/client/src/app/+admin/follows/follows.component.ts
new file mode 100644
index 000000000..97422a41b
--- /dev/null
+++ b/client/src/app/+admin/follows/follows.component.ts
@@ -0,0 +1,43 @@
1import { AfterViewInit, Component, ViewChild } from '@angular/core'
2import { TabsetComponent } from 'ngx-bootstrap/tabs'
3
4@Component({
5 templateUrl: './follows.component.html',
6 styleUrls: [ './follows.component.scss' ]
7})
8export class FollowsComponent implements AfterViewInit {
9 @ViewChild('followsMenuTabs') followsMenuTabs: TabsetComponent
10
11 links = [
12 {
13 path: 'following-list',
14 title: 'Following'
15 },
16 {
17 path: 'following-add',
18 title: 'Follow'
19 },
20 {
21 path: 'followers-list',
22 title: 'Followers'
23 }
24 ]
25
26 ngAfterViewInit () {
27 // Avoid issue with change detector
28 setTimeout(() => this.updateActiveTab())
29 }
30
31 private updateActiveTab () {
32 const url = window.location.pathname
33
34 for (let i = 0; i < this.links.length; i++) {
35 const path = this.links[i].path
36
37 if (url.endsWith(path) === true) {
38 this.followsMenuTabs.tabs[i].active = true
39 return
40 }
41 }
42 }
43}
diff --git a/client/src/app/+admin/follows/follows.routes.ts b/client/src/app/+admin/follows/follows.routes.ts
new file mode 100644
index 000000000..b7d44f75b
--- /dev/null
+++ b/client/src/app/+admin/follows/follows.routes.ts
@@ -0,0 +1,53 @@
1import { Routes } from '@angular/router'
2
3import { UserRightGuard } from '../../core'
4import { FollowsComponent } from './follows.component'
5import { FollowingAddComponent } from './following-add'
6import { FollowersListComponent } from './followers-list'
7import { UserRight } from '../../../../../shared'
8import { FollowingListComponent } from './following-list/following-list.component'
9
10export const FollowsRoutes: Routes = [
11 {
12 path: 'follows',
13 component: FollowsComponent,
14 canActivate: [ UserRightGuard ],
15 data: {
16 userRight: UserRight.MANAGE_APPLICATION_FOLLOW
17 },
18 children: [
19 {
20 path: '',
21 redirectTo: 'following-list',
22 pathMatch: 'full'
23 },
24 {
25 path: 'following-list',
26 component: FollowingListComponent,
27 data: {
28 meta: {
29 title: 'Following list'
30 }
31 }
32 },
33 {
34 path: 'followers-list',
35 component: FollowersListComponent,
36 data: {
37 meta: {
38 title: 'Followers list'
39 }
40 }
41 },
42 {
43 path: 'following-add',
44 component: FollowingAddComponent,
45 data: {
46 meta: {
47 title: 'Add follow'
48 }
49 }
50 }
51 ]
52 }
53]
diff --git a/client/src/app/+admin/follows/index.ts b/client/src/app/+admin/follows/index.ts
new file mode 100644
index 000000000..7849a06e7
--- /dev/null
+++ b/client/src/app/+admin/follows/index.ts
@@ -0,0 +1,6 @@
1export * from './following-add'
2export * from './followers-list'
3export * from './following-list'
4export * from './shared'
5export * from './follows.component'
6export * from './follows.routes'
diff --git a/client/src/app/+admin/friends/shared/friend.service.ts b/client/src/app/+admin/follows/shared/follow.service.ts
index 867656a53..622c33cea 100644
--- a/client/src/app/+admin/friends/shared/friend.service.ts
+++ b/client/src/app/+admin/follows/shared/follow.service.ts
@@ -10,8 +10,8 @@ import { RestExtractor, RestPagination, RestService } from '../../../shared'
10import { Pod, ResultList } from '../../../../../../shared' 10import { Pod, ResultList } from '../../../../../../shared'
11 11
12@Injectable() 12@Injectable()
13export class FriendService { 13export class FollowService {
14 private static BASE_FRIEND_URL = API_URL + '/api/v1/pods/' 14 private static BASE_APPLICATION_URL = API_URL + '/api/v1/application'
15 15
16 constructor ( 16 constructor (
17 private authHttp: HttpClient, 17 private authHttp: HttpClient,
@@ -23,29 +23,26 @@ export class FriendService {
23 let params = new HttpParams() 23 let params = new HttpParams()
24 params = this.restService.addRestGetParams(params, pagination, sort) 24 params = this.restService.addRestGetParams(params, pagination, sort)
25 25
26 return this.authHttp.get<ResultList<Account>>(API_URL + '/api/v1/pods/followers', { params }) 26 return this.authHttp.get<ResultList<Account>>(FollowService.BASE_APPLICATION_URL + '/following', { params })
27 .map(res => this.restExtractor.convertResultListDateToHuman(res)) 27 .map(res => this.restExtractor.convertResultListDateToHuman(res))
28 .catch(res => this.restExtractor.handleError(res)) 28 .catch(res => this.restExtractor.handleError(res))
29 } 29 }
30 30
31 getFollowers (pagination: RestPagination, sort: SortMeta): Observable<ResultList<Pod>> {
32 let params = new HttpParams()
33 params = this.restService.addRestGetParams(params, pagination, sort)
34
35 return this.authHttp.get<ResultList<Account>>(FollowService.BASE_APPLICATION_URL + '/followers', { params })
36 .map(res => this.restExtractor.convertResultListDateToHuman(res))
37 .catch(res => this.restExtractor.handleError(res))
38 }
39
31 follow (notEmptyHosts: String[]) { 40 follow (notEmptyHosts: String[]) {
32 const body = { 41 const body = {
33 hosts: notEmptyHosts 42 hosts: notEmptyHosts
34 } 43 }
35 44
36 return this.authHttp.post(API_URL + '/api/v1/pods/follow', body) 45 return this.authHttp.post(FollowService.BASE_APPLICATION_URL + '/follow', body)
37 .map(this.restExtractor.extractDataBool)
38 .catch(res => this.restExtractor.handleError(res))
39 }
40
41 quitFriends () {
42 return this.authHttp.get(FriendService.BASE_FRIEND_URL + 'quit-friends')
43 .map(this.restExtractor.extractDataBool)
44 .catch(res => this.restExtractor.handleError(res))
45 }
46
47 removeFriend (friend: Pod) {
48 return this.authHttp.delete(FriendService.BASE_FRIEND_URL + friend.id)
49 .map(this.restExtractor.extractDataBool) 46 .map(this.restExtractor.extractDataBool)
50 .catch(res => this.restExtractor.handleError(res)) 47 .catch(res => this.restExtractor.handleError(res))
51 } 48 }
diff --git a/client/src/app/+admin/follows/shared/index.ts b/client/src/app/+admin/follows/shared/index.ts
new file mode 100644
index 000000000..78d456def
--- /dev/null
+++ b/client/src/app/+admin/follows/shared/index.ts
@@ -0,0 +1 @@
export * from './follow.service'
diff --git a/client/src/app/+admin/friends/friend-add/index.ts b/client/src/app/+admin/friends/friend-add/index.ts
deleted file mode 100644
index 978ab3d46..000000000
--- a/client/src/app/+admin/friends/friend-add/index.ts
+++ /dev/null
@@ -1 +0,0 @@
1export * from './friend-add.component'
diff --git a/client/src/app/+admin/friends/friend-list/friend-list.component.html b/client/src/app/+admin/friends/friend-list/friend-list.component.html
deleted file mode 100644
index df5a570fd..000000000
--- a/client/src/app/+admin/friends/friend-list/friend-list.component.html
+++ /dev/null
@@ -1,29 +0,0 @@
1<div class="row">
2 <div class="content-padding">
3 <h3>Friends list</h3>
4
5 <p-dataTable
6 [value]="friends" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage"
7 sortField="createdAt" (onLazyLoad)="loadLazy($event)"
8 >
9 <p-column field="id" header="ID"></p-column>
10 <p-column field="host" header="Host"></p-column>
11 <p-column field="email" header="Email"></p-column>
12 <p-column field="score" header="Score"></p-column>
13 <p-column field="createdAt" header="Created date" [sortable]="true"></p-column>
14 <p-column header="Delete" styleClass="action-cell">
15 <ng-template pTemplate="body" let-pod="rowData">
16 <span (click)="removeFriend(pod)" class="glyphicon glyphicon-remove glyphicon-black" title="Remove this pod"></span>
17 </ng-template>
18 </p-column>
19 </p-dataTable>
20
21 <a *ngIf="hasFriends()" class="btn btn-danger pull-left" (click)="quitFriends()">
22 Quit friends
23 </a>
24
25 <a *ngIf="!hasFriends()" class="btn btn-success pull-right" [routerLink]="[ '/admin/friends/add' ]">
26 Make friends
27 </a>
28 </div>
29</div>
diff --git a/client/src/app/+admin/friends/friend-list/friend-list.component.ts b/client/src/app/+admin/friends/friend-list/friend-list.component.ts
deleted file mode 100644
index 3fa8ef19f..000000000
--- a/client/src/app/+admin/friends/friend-list/friend-list.component.ts
+++ /dev/null
@@ -1,87 +0,0 @@
1import { Component, OnInit } from '@angular/core'
2
3import { NotificationsService } from 'angular2-notifications'
4import { SortMeta } from 'primeng/primeng'
5
6import { ConfirmService } from '../../../core'
7import { RestTable, RestPagination } from '../../../shared'
8import { Pod } from '../../../../../../shared'
9import { FriendService } from '../shared'
10
11@Component({
12 selector: 'my-friend-list',
13 templateUrl: './friend-list.component.html',
14 styleUrls: ['./friend-list.component.scss']
15})
16export class FriendListComponent extends RestTable implements OnInit {
17 friends: Pod[] = []
18 totalRecords = 0
19 rowsPerPage = 10
20 sort: SortMeta = { field: 'createdAt', order: 1 }
21 pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
22
23 constructor (
24 private notificationsService: NotificationsService,
25 private confirmService: ConfirmService,
26 private friendService: FriendService
27 ) {
28 super()
29 }
30
31 ngOnInit () {
32 this.loadData()
33 }
34
35 hasFriends () {
36 return this.friends.length !== 0
37 }
38
39 quitFriends () {
40 const confirmMessage = 'Do you really want to quit your friends? All their videos will be deleted.'
41 this.confirmService.confirm(confirmMessage, 'Quit friends').subscribe(
42 res => {
43 if (res === false) return
44
45 this.friendService.quitFriends().subscribe(
46 status => {
47 this.notificationsService.success('Success', 'Friends left!')
48 this.loadData()
49 },
50
51 err => this.notificationsService.error('Error', err.message)
52 )
53 }
54 )
55 }
56
57 removeFriend (friend: Pod) {
58 const confirmMessage = 'Do you really want to remove this friend ? All its videos will be deleted.'
59
60 this.confirmService.confirm(confirmMessage, 'Remove').subscribe(
61 res => {
62 if (res === false) return
63
64 this.friendService.removeFriend(friend).subscribe(
65 status => {
66 this.notificationsService.success('Success', 'Friend removed')
67 this.loadData()
68 },
69
70 err => this.notificationsService.error('Error', err.message)
71 )
72 }
73 )
74 }
75
76 protected loadData () {
77 this.friendService.getFollowing(this.pagination, this.sort)
78 .subscribe(
79 resultList => {
80 this.friends = resultList.data
81 this.totalRecords = resultList.total
82 },
83
84 err => this.notificationsService.error('Error', err.message)
85 )
86 }
87}
diff --git a/client/src/app/+admin/friends/friend-list/index.ts b/client/src/app/+admin/friends/friend-list/index.ts
deleted file mode 100644
index c9cbd2800..000000000
--- a/client/src/app/+admin/friends/friend-list/index.ts
+++ /dev/null
@@ -1 +0,0 @@
1export * from './friend-list.component'
diff --git a/client/src/app/+admin/friends/friends.component.ts b/client/src/app/+admin/friends/friends.component.ts
deleted file mode 100644
index 5ef0aaa03..000000000
--- a/client/src/app/+admin/friends/friends.component.ts
+++ /dev/null
@@ -1,7 +0,0 @@
1import { Component } from '@angular/core'
2
3@Component({
4 template: '<router-outlet></router-outlet>'
5})
6export class FriendsComponent {
7}
diff --git a/client/src/app/+admin/friends/friends.routes.ts b/client/src/app/+admin/friends/friends.routes.ts
deleted file mode 100644
index e2cb953b3..000000000
--- a/client/src/app/+admin/friends/friends.routes.ts
+++ /dev/null
@@ -1,43 +0,0 @@
1import { Routes } from '@angular/router'
2
3import { UserRightGuard } from '../../core'
4import { FriendsComponent } from './friends.component'
5import { FriendAddComponent } from './friend-add'
6import { FriendListComponent } from './friend-list'
7import { UserRight } from '../../../../../shared'
8
9export const FriendsRoutes: Routes = [
10 {
11 path: 'friends',
12 component: FriendsComponent,
13 canActivate: [ UserRightGuard ],
14 data: {
15 userRight: UserRight.MANAGE_PEERTUBE_FOLLOW
16 },
17 children: [
18 {
19 path: '',
20 redirectTo: 'list',
21 pathMatch: 'full'
22 },
23 {
24 path: 'list',
25 component: FriendListComponent,
26 data: {
27 meta: {
28 title: 'Friends list'
29 }
30 }
31 },
32 {
33 path: 'add',
34 component: FriendAddComponent,
35 data: {
36 meta: {
37 title: 'Add friends'
38 }
39 }
40 }
41 ]
42 }
43]
diff --git a/client/src/app/+admin/friends/index.ts b/client/src/app/+admin/friends/index.ts
deleted file mode 100644
index 356dee8e9..000000000
--- a/client/src/app/+admin/friends/index.ts
+++ /dev/null
@@ -1,5 +0,0 @@
1export * from './friend-add'
2export * from './friend-list'
3export * from './shared'
4export * from './friends.component'
5export * from './friends.routes'
diff --git a/client/src/app/+admin/friends/shared/index.ts b/client/src/app/+admin/friends/shared/index.ts
deleted file mode 100644
index 65ab9fb46..000000000
--- a/client/src/app/+admin/friends/shared/index.ts
+++ /dev/null
@@ -1 +0,0 @@
1export * from './friend.service'
diff --git a/client/src/app/core/menu/menu-admin.component.html b/client/src/app/core/menu/menu-admin.component.html
index 1966a944c..99ee287c5 100644
--- a/client/src/app/core/menu/menu-admin.component.html
+++ b/client/src/app/core/menu/menu-admin.component.html
@@ -5,9 +5,9 @@
5 List users 5 List users
6 </a> 6 </a>
7 7
8 <a *ngIf="hasFriendsRight()" routerLink="/admin/friends" routerLinkActive="active"> 8 <a *ngIf="hasApplicationFollowRight()" routerLink="/admin/follows" routerLinkActive="active">
9 <span class="hidden-xs glyphicon glyphicon-cloud"></span> 9 <span class="hidden-xs glyphicon glyphicon-cloud"></span>
10 List friends 10 Manage follows
11 </a> 11 </a>
12 12
13 <a *ngIf="hasVideoAbusesRight()" routerLink="/admin/video-abuses" routerLinkActive="active"> 13 <a *ngIf="hasVideoAbusesRight()" routerLink="/admin/video-abuses" routerLinkActive="active">
diff --git a/client/src/app/core/menu/menu-admin.component.ts b/client/src/app/core/menu/menu-admin.component.ts
index 92aab9a05..88a654d1f 100644
--- a/client/src/app/core/menu/menu-admin.component.ts
+++ b/client/src/app/core/menu/menu-admin.component.ts
@@ -15,8 +15,8 @@ export class MenuAdminComponent {
15 return this.auth.getUser().hasRight(UserRight.MANAGE_USERS) 15 return this.auth.getUser().hasRight(UserRight.MANAGE_USERS)
16 } 16 }
17 17
18 hasFriendsRight () { 18 hasApplicationFollowRight () {
19 return this.auth.getUser().hasRight(UserRight.MANAGE_PEERTUBE_FOLLOW) 19 return this.auth.getUser().hasRight(UserRight.MANAGE_APPLICATION_FOLLOW)
20 } 20 }
21 21
22 hasVideoAbusesRight () { 22 hasVideoAbusesRight () {
diff --git a/client/src/app/core/menu/menu.component.ts b/client/src/app/core/menu/menu.component.ts
index 71295be86..872d29819 100644
--- a/client/src/app/core/menu/menu.component.ts
+++ b/client/src/app/core/menu/menu.component.ts
@@ -16,7 +16,7 @@ export class MenuComponent implements OnInit {
16 16
17 private routesPerRight = { 17 private routesPerRight = {
18 [UserRight.MANAGE_USERS]: '/admin/users', 18 [UserRight.MANAGE_USERS]: '/admin/users',
19 [UserRight.MANAGE_PEERTUBE_FOLLOW]: '/admin/friends', 19 [UserRight.MANAGE_APPLICATION_FOLLOW]: '/admin/friends',
20 [UserRight.MANAGE_VIDEO_ABUSES]: '/admin/video-abuses', 20 [UserRight.MANAGE_VIDEO_ABUSES]: '/admin/video-abuses',
21 [UserRight.MANAGE_VIDEO_BLACKLIST]: '/admin/video-blacklist' 21 [UserRight.MANAGE_VIDEO_BLACKLIST]: '/admin/video-blacklist'
22 } 22 }
@@ -58,7 +58,7 @@ export class MenuComponent implements OnInit {
58 58
59 const adminRights = [ 59 const adminRights = [
60 UserRight.MANAGE_USERS, 60 UserRight.MANAGE_USERS,
61 UserRight.MANAGE_PEERTUBE_FOLLOW, 61 UserRight.MANAGE_APPLICATION_FOLLOW,
62 UserRight.MANAGE_VIDEO_ABUSES, 62 UserRight.MANAGE_VIDEO_ABUSES,
63 UserRight.MANAGE_VIDEO_BLACKLIST 63 UserRight.MANAGE_VIDEO_BLACKLIST
64 ] 64 ]
diff --git a/scripts/update-host.ts b/scripts/update-host.ts
index 7c46dc52b..05f0ef96d 100755
--- a/scripts/update-host.ts
+++ b/scripts/update-host.ts
@@ -3,7 +3,7 @@ import { database as db } from '../server/initializers/database'
3 3
4db.init(true) 4db.init(true)
5 .then(() => { 5 .then(() => {
6 // FIXME: check if has followers 6 // FIXME: check if has following
7 // return hasFriends() 7 // return hasFriends()
8 return true 8 return true
9 }) 9 })
diff --git a/server/controllers/activitypub/client.ts b/server/controllers/activitypub/client.ts
index 56a4054fa..49dd24e79 100644
--- a/server/controllers/activitypub/client.ts
+++ b/server/controllers/activitypub/client.ts
@@ -46,7 +46,7 @@ async function accountFollowersController (req: express.Request, res: express.Re
46 const page = req.params.page || 1 46 const page = req.params.page || 1
47 const { start, count } = pageToStartAndCount(page, ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE) 47 const { start, count } = pageToStartAndCount(page, ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE)
48 48
49 const result = await db.Account.listAcceptedFollowerUrlsForApi(account.id, start, count) 49 const result = await db.AccountFollow.listAcceptedFollowerUrlsForApi(account.id, start, count)
50 const activityPubResult = activityPubCollectionPagination(req.url, page, result) 50 const activityPubResult = activityPubCollectionPagination(req.url, page, result)
51 51
52 return res.json(activityPubResult) 52 return res.json(activityPubResult)
@@ -58,7 +58,7 @@ async function accountFollowingController (req: express.Request, res: express.Re
58 const page = req.params.page || 1 58 const page = req.params.page || 1
59 const { start, count } = pageToStartAndCount(page, ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE) 59 const { start, count } = pageToStartAndCount(page, ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE)
60 60
61 const result = await db.Account.listAcceptedFollowingUrlsForApi(account.id, start, count) 61 const result = await db.AccountFollow.listAcceptedFollowingUrlsForApi(account.id, start, count)
62 const activityPubResult = activityPubCollectionPagination(req.url, page, result) 62 const activityPubResult = activityPubCollectionPagination(req.url, page, result)
63 63
64 return res.json(activityPubResult) 64 return res.json(activityPubResult)
diff --git a/server/controllers/activitypub/index.ts b/server/controllers/activitypub/index.ts
index 0c8574ef7..c5bec6448 100644
--- a/server/controllers/activitypub/index.ts
+++ b/server/controllers/activitypub/index.ts
@@ -1,14 +1,11 @@
1import * as express from 'express' 1import * as express from 'express'
2
3import { badRequest } from '../../helpers'
4import { inboxRouter } from './inbox'
5import { activityPubClientRouter } from './client' 2import { activityPubClientRouter } from './client'
3import { inboxRouter } from './inbox'
6 4
7const activityPubRouter = express.Router() 5const activityPubRouter = express.Router()
8 6
9activityPubRouter.use('/', inboxRouter) 7activityPubRouter.use('/', inboxRouter)
10activityPubRouter.use('/', activityPubClientRouter) 8activityPubRouter.use('/', activityPubClientRouter)
11activityPubRouter.use('/*', badRequest)
12 9
13// --------------------------------------------------------------------------- 10// ---------------------------------------------------------------------------
14 11
diff --git a/server/controllers/api/pods.ts b/server/controllers/api/application/follows.ts
index 0bd6971bb..000bbd23e 100644
--- a/server/controllers/api/pods.ts
+++ b/server/controllers/api/application/follows.ts
@@ -1,23 +1,23 @@
1import * as express from 'express' 1import * as express from 'express'
2import { UserRight } from '../../../shared/models/users/user-right.enum' 2import { UserRight } from '../../../../shared/models/users/user-right.enum'
3import { getFormattedObjects } from '../../helpers' 3import { getFormattedObjects } from '../../../helpers'
4import { logger } from '../../helpers/logger' 4import { logger } from '../../../helpers/logger'
5import { getApplicationAccount } from '../../helpers/utils' 5import { getApplicationAccount } from '../../../helpers/utils'
6import { getAccountFromWebfinger } from '../../helpers/webfinger' 6import { getAccountFromWebfinger } from '../../../helpers/webfinger'
7import { SERVER_ACCOUNT_NAME } from '../../initializers/constants' 7import { SERVER_ACCOUNT_NAME } from '../../../initializers/constants'
8import { database as db } from '../../initializers/database' 8import { database as db } from '../../../initializers/database'
9import { sendFollow } from '../../lib/activitypub/send-request' 9import { sendFollow } from '../../../lib/activitypub/send-request'
10import { asyncMiddleware, paginationValidator, setFollowersSort, setPagination } from '../../middlewares' 10import { asyncMiddleware, paginationValidator, setFollowersSort, setPagination } from '../../../middlewares'
11import { authenticate } from '../../middlewares/oauth' 11import { authenticate } from '../../../middlewares/oauth'
12import { setBodyHostsPort } from '../../middlewares/pods' 12import { setBodyHostsPort } from '../../../middlewares/pods'
13import { setFollowingSort } from '../../middlewares/sort' 13import { setFollowingSort } from '../../../middlewares/sort'
14import { ensureUserHasRight } from '../../middlewares/user-right' 14import { ensureUserHasRight } from '../../../middlewares/user-right'
15import { followValidator } from '../../middlewares/validators/pods' 15import { followValidator } from '../../../middlewares/validators/pods'
16import { followersSortValidator, followingSortValidator } from '../../middlewares/validators/sort' 16import { followersSortValidator, followingSortValidator } from '../../../middlewares/validators/sort'
17 17
18const podsRouter = express.Router() 18const applicationFollowsRouter = express.Router()
19 19
20podsRouter.get('/following', 20applicationFollowsRouter.get('/following',
21 paginationValidator, 21 paginationValidator,
22 followingSortValidator, 22 followingSortValidator,
23 setFollowingSort, 23 setFollowingSort,
@@ -25,15 +25,15 @@ podsRouter.get('/following',
25 asyncMiddleware(listFollowing) 25 asyncMiddleware(listFollowing)
26) 26)
27 27
28podsRouter.post('/follow', 28applicationFollowsRouter.post('/follow',
29 authenticate, 29 authenticate,
30 ensureUserHasRight(UserRight.MANAGE_PEERTUBE_FOLLOW), 30 ensureUserHasRight(UserRight.MANAGE_APPLICATION_FOLLOW),
31 followValidator, 31 followValidator,
32 setBodyHostsPort, 32 setBodyHostsPort,
33 asyncMiddleware(follow) 33 asyncMiddleware(follow)
34) 34)
35 35
36podsRouter.get('/followers', 36applicationFollowsRouter.get('/followers',
37 paginationValidator, 37 paginationValidator,
38 followersSortValidator, 38 followersSortValidator,
39 setFollowersSort, 39 setFollowersSort,
@@ -44,21 +44,21 @@ podsRouter.get('/followers',
44// --------------------------------------------------------------------------- 44// ---------------------------------------------------------------------------
45 45
46export { 46export {
47 podsRouter 47 applicationFollowsRouter
48} 48}
49 49
50// --------------------------------------------------------------------------- 50// ---------------------------------------------------------------------------
51 51
52async function listFollowing (req: express.Request, res: express.Response, next: express.NextFunction) { 52async function listFollowing (req: express.Request, res: express.Response, next: express.NextFunction) {
53 const applicationAccount = await getApplicationAccount() 53 const applicationAccount = await getApplicationAccount()
54 const resultList = await db.Account.listFollowingForApi(applicationAccount.id, req.query.start, req.query.count, req.query.sort) 54 const resultList = await db.AccountFollow.listFollowingForApi(applicationAccount.id, req.query.start, req.query.count, req.query.sort)
55 55
56 return res.json(getFormattedObjects(resultList.data, resultList.total)) 56 return res.json(getFormattedObjects(resultList.data, resultList.total))
57} 57}
58 58
59async function listFollowers (req: express.Request, res: express.Response, next: express.NextFunction) { 59async function listFollowers (req: express.Request, res: express.Response, next: express.NextFunction) {
60 const applicationAccount = await getApplicationAccount() 60 const applicationAccount = await getApplicationAccount()
61 const resultList = await db.Account.listFollowersForApi(applicationAccount.id, req.query.start, req.query.count, req.query.sort) 61 const resultList = await db.AccountFollow.listFollowersForApi(applicationAccount.id, req.query.start, req.query.count, req.query.sort)
62 62
63 return res.json(getFormattedObjects(resultList.data, resultList.total)) 63 return res.json(getFormattedObjects(resultList.data, resultList.total))
64} 64}
diff --git a/server/controllers/api/application/index.ts b/server/controllers/api/application/index.ts
new file mode 100644
index 000000000..011b971ed
--- /dev/null
+++ b/server/controllers/api/application/index.ts
@@ -0,0 +1,12 @@
1import * as express from 'express'
2import { applicationFollowsRouter } from './follows'
3
4const applicationRouter = express.Router()
5
6applicationRouter.use('/', applicationFollowsRouter)
7
8// ---------------------------------------------------------------------------
9
10export {
11 applicationRouter
12}
diff --git a/server/controllers/api/index.ts b/server/controllers/api/index.ts
index 2e949d531..a22c78cce 100644
--- a/server/controllers/api/index.ts
+++ b/server/controllers/api/index.ts
@@ -4,15 +4,15 @@ import { badRequest } from '../../helpers'
4 4
5import { oauthClientsRouter } from './oauth-clients' 5import { oauthClientsRouter } from './oauth-clients'
6import { configRouter } from './config' 6import { configRouter } from './config'
7import { podsRouter } from './pods' 7import { applicationRouter } from './application'
8import { usersRouter } from './users' 8import { usersRouter } from './users'
9import { videosRouter } from './videos' 9import { videosRouter } from './videos'
10 10
11const apiRouter = express.Router() 11const apiRouter = express.Router()
12 12
13apiRouter.use('/application', applicationRouter)
13apiRouter.use('/oauth-clients', oauthClientsRouter) 14apiRouter.use('/oauth-clients', oauthClientsRouter)
14apiRouter.use('/config', configRouter) 15apiRouter.use('/config', configRouter)
15apiRouter.use('/pods', podsRouter)
16apiRouter.use('/users', usersRouter) 16apiRouter.use('/users', usersRouter)
17apiRouter.use('/videos', videosRouter) 17apiRouter.use('/videos', videosRouter)
18apiRouter.use('/ping', pong) 18apiRouter.use('/ping', pong)
diff --git a/server/controllers/webfinger.ts b/server/controllers/webfinger.ts
index 1c726f0cb..102ac0937 100644
--- a/server/controllers/webfinger.ts
+++ b/server/controllers/webfinger.ts
@@ -8,7 +8,7 @@ import { AccountInstance } from '../models/account/account-interface'
8 8
9const webfingerRouter = express.Router() 9const webfingerRouter = express.Router()
10 10
11webfingerRouter.use('/.well-known/webfinger', 11webfingerRouter.get('/.well-known/webfinger',
12 webfingerValidator, 12 webfingerValidator,
13 webfingerController 13 webfingerController
14) 14)
diff --git a/server/lib/activitypub/send-request.ts b/server/lib/activitypub/send-request.ts
index d47040d6d..f942a2eba 100644
--- a/server/lib/activitypub/send-request.ts
+++ b/server/lib/activitypub/send-request.ts
@@ -85,7 +85,7 @@ export {
85// --------------------------------------------------------------------------- 85// ---------------------------------------------------------------------------
86 86
87async function broadcastToFollowers (data: any, fromAccount: AccountInstance, t: Sequelize.Transaction) { 87async function broadcastToFollowers (data: any, fromAccount: AccountInstance, t: Sequelize.Transaction) {
88 const result = await db.Account.listAcceptedFollowerUrlsForApi(fromAccount.id, 0) 88 const result = await db.AccountFollow.listAcceptedFollowerUrlsForApi(fromAccount.id, 0)
89 89
90 const jobPayload = { 90 const jobPayload = {
91 uris: result.data, 91 uris: result.data,
diff --git a/server/models/account/account-follow-interface.ts b/server/models/account/account-follow-interface.ts
index efdff915e..413dad190 100644
--- a/server/models/account/account-follow-interface.ts
+++ b/server/models/account/account-follow-interface.ts
@@ -1,13 +1,26 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2import * as Bluebird from 'bluebird' 2import * as Bluebird from 'bluebird'
3import { FollowState } from '../../../shared/models/accounts/follow.model' 3import { FollowState } from '../../../shared/models/accounts/follow.model'
4import { ResultList } from '../../../shared/models/result-list.model'
5import { AccountInstance } from './account-interface'
4 6
5export namespace AccountFollowMethods { 7export namespace AccountFollowMethods {
6 export type LoadByAccountAndTarget = (accountId: number, targetAccountId: number) => Bluebird<AccountFollowInstance> 8 export type LoadByAccountAndTarget = (accountId: number, targetAccountId: number) => Bluebird<AccountFollowInstance>
9
10 export type ListFollowingForApi = (id: number, start: number, count: number, sort: string) => Bluebird< ResultList<AccountInstance> >
11 export type ListFollowersForApi = (id: number, start: number, count: number, sort: string) => Bluebird< ResultList<AccountInstance> >
12
13 export type ListAcceptedFollowerUrlsForApi = (id: number, start: number, count?: number) => Promise< ResultList<string> >
14 export type ListAcceptedFollowingUrlsForApi = (id: number, start: number, count?: number) => Promise< ResultList<string> >
7} 15}
8 16
9export interface AccountFollowClass { 17export interface AccountFollowClass {
10 loadByAccountAndTarget: AccountFollowMethods.LoadByAccountAndTarget 18 loadByAccountAndTarget: AccountFollowMethods.LoadByAccountAndTarget
19 listFollowersForApi: AccountFollowMethods.ListFollowersForApi
20 listFollowingForApi: AccountFollowMethods.ListFollowingForApi
21
22 listAcceptedFollowerUrlsForApi: AccountFollowMethods.ListAcceptedFollowerUrlsForApi
23 listAcceptedFollowingUrlsForApi: AccountFollowMethods.ListAcceptedFollowingUrlsForApi
11} 24}
12 25
13export interface AccountFollowAttributes { 26export interface AccountFollowAttributes {
@@ -20,6 +33,9 @@ export interface AccountFollowInstance extends AccountFollowClass, AccountFollow
20 id: number 33 id: number
21 createdAt: Date 34 createdAt: Date
22 updatedAt: Date 35 updatedAt: Date
36
37 AccountFollower?: AccountInstance
38 AccountFollowing?: AccountInstance
23} 39}
24 40
25export interface AccountFollowModel extends AccountFollowClass, Sequelize.Model<AccountFollowInstance, AccountFollowAttributes> {} 41export interface AccountFollowModel extends AccountFollowClass, Sequelize.Model<AccountFollowInstance, AccountFollowAttributes> {}
diff --git a/server/models/account/account-follow.ts b/server/models/account/account-follow.ts
index 7c129ab9d..6d7592326 100644
--- a/server/models/account/account-follow.ts
+++ b/server/models/account/account-follow.ts
@@ -1,12 +1,16 @@
1import { values } from 'lodash' 1import { values } from 'lodash'
2import * as Sequelize from 'sequelize' 2import * as Sequelize from 'sequelize'
3 3
4import { addMethodsToModel } from '../utils' 4import { addMethodsToModel, getSort } from '../utils'
5import { AccountFollowAttributes, AccountFollowInstance, AccountFollowMethods } from './account-follow-interface' 5import { AccountFollowAttributes, AccountFollowInstance, AccountFollowMethods } from './account-follow-interface'
6import { FOLLOW_STATES } from '../../initializers/constants' 6import { FOLLOW_STATES } from '../../initializers/constants'
7 7
8let AccountFollow: Sequelize.Model<AccountFollowInstance, AccountFollowAttributes> 8let AccountFollow: Sequelize.Model<AccountFollowInstance, AccountFollowAttributes>
9let loadByAccountAndTarget: AccountFollowMethods.LoadByAccountAndTarget 9let loadByAccountAndTarget: AccountFollowMethods.LoadByAccountAndTarget
10let listFollowingForApi: AccountFollowMethods.ListFollowingForApi
11let listFollowersForApi: AccountFollowMethods.ListFollowersForApi
12let listAcceptedFollowerUrlsForApi: AccountFollowMethods.ListAcceptedFollowerUrlsForApi
13let listAcceptedFollowingUrlsForApi: AccountFollowMethods.ListAcceptedFollowingUrlsForApi
10 14
11export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { 15export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
12 AccountFollow = sequelize.define<AccountFollowInstance, AccountFollowAttributes>('AccountFollow', 16 AccountFollow = sequelize.define<AccountFollowInstance, AccountFollowAttributes>('AccountFollow',
@@ -34,7 +38,11 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
34 38
35 const classMethods = [ 39 const classMethods = [
36 associate, 40 associate,
37 loadByAccountAndTarget 41 loadByAccountAndTarget,
42 listFollowingForApi,
43 listFollowersForApi,
44 listAcceptedFollowerUrlsForApi,
45 listAcceptedFollowingUrlsForApi
38 ] 46 ]
39 addMethodsToModel(AccountFollow, classMethods) 47 addMethodsToModel(AccountFollow, classMethods)
40 48
@@ -49,7 +57,7 @@ function associate (models) {
49 name: 'accountId', 57 name: 'accountId',
50 allowNull: false 58 allowNull: false
51 }, 59 },
52 as: 'accountFollowers', 60 as: 'AccountFollower',
53 onDelete: 'CASCADE' 61 onDelete: 'CASCADE'
54 }) 62 })
55 63
@@ -58,7 +66,7 @@ function associate (models) {
58 name: 'targetAccountId', 66 name: 'targetAccountId',
59 allowNull: false 67 allowNull: false
60 }, 68 },
61 as: 'accountFollowing', 69 as: 'AccountFollowing',
62 onDelete: 'CASCADE' 70 onDelete: 'CASCADE'
63 }) 71 })
64} 72}
@@ -73,3 +81,117 @@ loadByAccountAndTarget = function (accountId: number, targetAccountId: number) {
73 81
74 return AccountFollow.findOne(query) 82 return AccountFollow.findOne(query)
75} 83}
84
85listFollowingForApi = function (id: number, start: number, count: number, sort: string) {
86 const query = {
87 distinct: true,
88 offset: start,
89 limit: count,
90 order: [ getSort(sort) ],
91 include: [
92 {
93 model: AccountFollow[ 'sequelize' ].models.Account,
94 required: true,
95 as: 'AccountFollower',
96 where: {
97 id
98 }
99 },
100 {
101 model: AccountFollow['sequelize'].models.Account,
102 as: 'AccountFollowing',
103 required: true,
104 include: [ AccountFollow['sequelize'].models.Pod ]
105 }
106 ]
107 }
108
109 return AccountFollow.findAndCountAll(query).then(({ rows, count }) => {
110 return {
111 data: rows.map(r => r.AccountFollowing),
112 total: count
113 }
114 })
115}
116
117listFollowersForApi = function (id: number, start: number, count: number, sort: string) {
118 const query = {
119 distinct: true,
120 offset: start,
121 limit: count,
122 order: [ getSort(sort) ],
123 include: [
124 {
125 model: AccountFollow[ 'sequelize' ].models.Account,
126 required: true,
127 as: 'AccountFollower',
128 include: [ AccountFollow['sequelize'].models.Pod ]
129 },
130 {
131 model: AccountFollow['sequelize'].models.Account,
132 as: 'AccountFollowing',
133 required: true,
134 where: {
135 id
136 }
137 }
138 ]
139 }
140
141 return AccountFollow.findAndCountAll(query).then(({ rows, count }) => {
142 return {
143 data: rows.map(r => r.AccountFollower),
144 total: count
145 }
146 })
147}
148
149listAcceptedFollowerUrlsForApi = function (id: number, start: number, count?: number) {
150 return createListAcceptedFollowForApiQuery('followers', id, start, count)
151}
152
153listAcceptedFollowingUrlsForApi = function (id: number, start: number, count?: number) {
154 return createListAcceptedFollowForApiQuery('following', id, start, count)
155}
156
157// ------------------------------ UTILS ------------------------------
158
159async function createListAcceptedFollowForApiQuery (type: 'followers' | 'following', id: number, start: number, count?: number) {
160 let firstJoin: string
161 let secondJoin: string
162
163 if (type === 'followers') {
164 firstJoin = 'targetAccountId'
165 secondJoin = 'accountId'
166 } else {
167 firstJoin = 'accountId'
168 secondJoin = 'targetAccountId'
169 }
170
171 const selections = [ '"Followers"."url" AS "url"', 'COUNT(*) AS "total"' ]
172 const tasks: Promise<any>[] = []
173
174 for (const selection of selections) {
175 let query = 'SELECT ' + selection + ' FROM "Account" ' +
176 'INNER JOIN "AccountFollow" ON "AccountFollow"."' + firstJoin + '" = "Account"."id" ' +
177 'INNER JOIN "Account" AS "Follows" ON "Followers"."id" = "Follows"."' + secondJoin + '" ' +
178 'WHERE "Account"."id" = $id AND "AccountFollow"."state" = \'accepted\' ' +
179 'LIMIT ' + start
180
181 if (count !== undefined) query += ', ' + count
182
183 const options = {
184 bind: { id },
185 type: Sequelize.QueryTypes.SELECT
186 }
187 tasks.push(AccountFollow['sequelize'].query(query, options))
188 }
189
190 const [ followers, [ { total } ]] = await Promise.all(tasks)
191 const urls: string[] = followers.map(f => f.url)
192
193 return {
194 data: urls,
195 total: parseInt(total, 10)
196 }
197}
diff --git a/server/models/account/account-interface.ts b/server/models/account/account-interface.ts
index 6fc36ae9d..ce1afec02 100644
--- a/server/models/account/account-interface.ts
+++ b/server/models/account/account-interface.ts
@@ -15,10 +15,6 @@ export namespace AccountMethods {
15 export type LoadLocalByName = (name: string) => Bluebird<AccountInstance> 15 export type LoadLocalByName = (name: string) => Bluebird<AccountInstance>
16 export type LoadByNameAndHost = (name: string, host: string) => Bluebird<AccountInstance> 16 export type LoadByNameAndHost = (name: string, host: string) => Bluebird<AccountInstance>
17 export type ListOwned = () => Bluebird<AccountInstance[]> 17 export type ListOwned = () => Bluebird<AccountInstance[]>
18 export type ListAcceptedFollowerUrlsForApi = (id: number, start: number, count?: number) => Promise< ResultList<string> >
19 export type ListAcceptedFollowingUrlsForApi = (id: number, start: number, count?: number) => Promise< ResultList<string> >
20 export type ListFollowingForApi = (id: number, start: number, count: number, sort: string) => Bluebird< ResultList<AccountInstance> >
21 export type ListFollowersForApi = (id: number, start: number, count: number, sort: string) => Bluebird< ResultList<AccountInstance> >
22 18
23 export type ToActivityPubObject = (this: AccountInstance) => ActivityPubActor 19 export type ToActivityPubObject = (this: AccountInstance) => ActivityPubActor
24 export type ToFormattedJSON = (this: AccountInstance) => FormattedAccount 20 export type ToFormattedJSON = (this: AccountInstance) => FormattedAccount
@@ -38,10 +34,6 @@ export interface AccountClass {
38 loadLocalByName: AccountMethods.LoadLocalByName 34 loadLocalByName: AccountMethods.LoadLocalByName
39 loadByNameAndHost: AccountMethods.LoadByNameAndHost 35 loadByNameAndHost: AccountMethods.LoadByNameAndHost
40 listOwned: AccountMethods.ListOwned 36 listOwned: AccountMethods.ListOwned
41 listAcceptedFollowerUrlsForApi: AccountMethods.ListAcceptedFollowerUrlsForApi
42 listAcceptedFollowingUrlsForApi: AccountMethods.ListAcceptedFollowingUrlsForApi
43 listFollowingForApi: AccountMethods.ListFollowingForApi
44 listFollowersForApi: AccountMethods.ListFollowersForApi
45} 37}
46 38
47export interface AccountAttributes { 39export interface AccountAttributes {
diff --git a/server/models/account/account.ts b/server/models/account/account.ts
index d2293a939..e90eaae5e 100644
--- a/server/models/account/account.ts
+++ b/server/models/account/account.ts
@@ -23,7 +23,7 @@ import {
23 AccountMethods 23 AccountMethods
24} from './account-interface' 24} from './account-interface'
25import { sendDeleteAccount } from '../../lib/activitypub/send-request' 25import { sendDeleteAccount } from '../../lib/activitypub/send-request'
26import { CONSTRAINTS_FIELDS } from '../../initializers/constants' 26import { CONFIG, CONSTRAINTS_FIELDS } from '../../initializers/constants'
27 27
28let Account: Sequelize.Model<AccountInstance, AccountAttributes> 28let Account: Sequelize.Model<AccountInstance, AccountAttributes>
29let loadAccountByPodAndUUID: AccountMethods.LoadAccountByPodAndUUID 29let loadAccountByPodAndUUID: AccountMethods.LoadAccountByPodAndUUID
@@ -34,10 +34,6 @@ let loadByUrl: AccountMethods.LoadByUrl
34let loadLocalByName: AccountMethods.LoadLocalByName 34let loadLocalByName: AccountMethods.LoadLocalByName
35let loadByNameAndHost: AccountMethods.LoadByNameAndHost 35let loadByNameAndHost: AccountMethods.LoadByNameAndHost
36let listOwned: AccountMethods.ListOwned 36let listOwned: AccountMethods.ListOwned
37let listAcceptedFollowerUrlsForApi: AccountMethods.ListAcceptedFollowerUrlsForApi
38let listAcceptedFollowingUrlsForApi: AccountMethods.ListAcceptedFollowingUrlsForApi
39let listFollowingForApi: AccountMethods.ListFollowingForApi
40let listFollowersForApi: AccountMethods.ListFollowersForApi
41let isOwned: AccountMethods.IsOwned 37let isOwned: AccountMethods.IsOwned
42let toActivityPubObject: AccountMethods.ToActivityPubObject 38let toActivityPubObject: AccountMethods.ToActivityPubObject
43let toFormattedJSON: AccountMethods.ToFormattedJSON 39let toFormattedJSON: AccountMethods.ToFormattedJSON
@@ -185,7 +181,7 @@ export default function defineAccount (sequelize: Sequelize.Sequelize, DataTypes
185 unique: true 181 unique: true
186 }, 182 },
187 { 183 {
188 fields: [ 'name', 'podId' ], 184 fields: [ 'name', 'podId', 'applicationId' ],
189 unique: true 185 unique: true
190 } 186 }
191 ], 187 ],
@@ -202,11 +198,7 @@ export default function defineAccount (sequelize: Sequelize.Sequelize, DataTypes
202 loadByUrl, 198 loadByUrl,
203 loadLocalByName, 199 loadLocalByName,
204 loadByNameAndHost, 200 loadByNameAndHost,
205 listOwned, 201 listOwned
206 listAcceptedFollowerUrlsForApi,
207 listAcceptedFollowingUrlsForApi,
208 listFollowingForApi,
209 listFollowersForApi
210 ] 202 ]
211 const instanceMethods = [ 203 const instanceMethods = [
212 isOwned, 204 isOwned,
@@ -286,9 +278,11 @@ function afterDestroy (account: AccountInstance) {
286} 278}
287 279
288toFormattedJSON = function (this: AccountInstance) { 280toFormattedJSON = function (this: AccountInstance) {
281 let host = this.Pod ? this.Pod.host : CONFIG.WEBSERVER.HOST
282
289 const json = { 283 const json = {
290 id: this.id, 284 id: this.id,
291 host: this.Pod.host, 285 host,
292 name: this.name 286 name: this.name
293 } 287 }
294 288
@@ -346,7 +340,7 @@ getFollowerSharedInboxUrls = function (this: AccountInstance) {
346} 340}
347 341
348getFollowingUrl = function (this: AccountInstance) { 342getFollowingUrl = function (this: AccountInstance) {
349 return this.url + '/followers' 343 return this.url + '/following'
350} 344}
351 345
352getFollowersUrl = function (this: AccountInstance) { 346getFollowersUrl = function (this: AccountInstance) {
@@ -369,76 +363,6 @@ listOwned = function () {
369 return Account.findAll(query) 363 return Account.findAll(query)
370} 364}
371 365
372listAcceptedFollowerUrlsForApi = function (id: number, start: number, count?: number) {
373 return createListAcceptedFollowForApiQuery('followers', id, start, count)
374}
375
376listAcceptedFollowingUrlsForApi = function (id: number, start: number, count?: number) {
377 return createListAcceptedFollowForApiQuery('following', id, start, count)
378}
379
380listFollowingForApi = function (id: number, start: number, count: number, sort: string) {
381 const query = {
382 distinct: true,
383 offset: start,
384 limit: count,
385 order: [ getSort(sort) ],
386 include: [
387 {
388 model: Account['sequelize'].models.AccountFollow,
389 required: true,
390 as: 'following',
391 include: [
392 {
393 model: Account['sequelize'].models.Account,
394 as: 'accountFollowing',
395 required: true,
396 include: [ Account['sequelize'].models.Pod ]
397 }
398 ]
399 }
400 ]
401 }
402
403 return Account.findAndCountAll(query).then(({ rows, count }) => {
404 return {
405 data: rows,
406 total: count
407 }
408 })
409}
410
411listFollowersForApi = function (id: number, start: number, count: number, sort: string) {
412 const query = {
413 distinct: true,
414 offset: start,
415 limit: count,
416 order: [ getSort(sort) ],
417 include: [
418 {
419 model: Account['sequelize'].models.AccountFollow,
420 required: true,
421 as: 'followers',
422 include: [
423 {
424 model: Account['sequelize'].models.Account,
425 as: 'accountFollowers',
426 required: true,
427 include: [ Account['sequelize'].models.Pod ]
428 }
429 ]
430 }
431 ]
432 }
433
434 return Account.findAndCountAll(query).then(({ rows, count }) => {
435 return {
436 data: rows,
437 total: count
438 }
439 })
440}
441
442loadApplication = function () { 366loadApplication = function () {
443 return Account.findOne({ 367 return Account.findOne({
444 include: [ 368 include: [
@@ -527,45 +451,3 @@ loadAccountByPodAndUUID = function (uuid: string, podId: number, transaction: Se
527 451
528 return Account.find(query) 452 return Account.find(query)
529} 453}
530
531// ------------------------------ UTILS ------------------------------
532
533async function createListAcceptedFollowForApiQuery (type: 'followers' | 'following', id: number, start: number, count?: number) {
534 let firstJoin: string
535 let secondJoin: string
536
537 if (type === 'followers') {
538 firstJoin = 'targetAccountId'
539 secondJoin = 'accountId'
540 } else {
541 firstJoin = 'accountId'
542 secondJoin = 'targetAccountId'
543 }
544
545 const selections = [ '"Followers"."url" AS "url"', 'COUNT(*) AS "total"' ]
546 const tasks: Promise<any>[] = []
547
548 for (const selection of selections) {
549 let query = 'SELECT ' + selection + ' FROM "Account" ' +
550 'INNER JOIN "AccountFollow" ON "AccountFollow"."' + firstJoin + '" = "Account"."id" ' +
551 'INNER JOIN "Account" AS "Follows" ON "Followers"."id" = "Follows"."' + secondJoin + '" ' +
552 'WHERE "Account"."id" = $id AND "AccountFollow"."state" = \'accepted\' ' +
553 'LIMIT ' + start
554
555 if (count !== undefined) query += ', ' + count
556
557 const options = {
558 bind: { id },
559 type: Sequelize.QueryTypes.SELECT
560 }
561 tasks.push(Account['sequelize'].query(query, options))
562 }
563
564 const [ followers, [ { total } ]] = await Promise.all(tasks)
565 const urls: string[] = followers.map(f => f.url)
566
567 return {
568 data: urls,
569 total: parseInt(total, 10)
570 }
571}
diff --git a/shared/models/users/user-right.enum.ts b/shared/models/users/user-right.enum.ts
index 9d5ebbb16..ecad69d6f 100644
--- a/shared/models/users/user-right.enum.ts
+++ b/shared/models/users/user-right.enum.ts
@@ -1,9 +1,9 @@
1export enum UserRight { 1export enum UserRight {
2 ALL, 2 ALL,
3 MANAGE_USERS, 3 MANAGE_USERS,
4 MANAGE_PEERTUBE_FOLLOW, 4 MANAGE_APPLICATION_FOLLOW,
5 MANAGE_VIDEO_ABUSES, 5 MANAGE_VIDEO_ABUSES,
6 MANAGE_VIDEO_BLACKLIST, 6 MANAGE_VIDEO_BLACKLIST,
7 REMOVE_ANY_VIDEO, 7 REMOVE_ANY_VIDEO,
8 REMOVE_ANY_VIDEO_CHANNEL, 8 REMOVE_ANY_VIDEO_CHANNEL
9} 9}