Add ability to unfollow a server
authorChocobozzz <florian.bigard@gmail.com>
Mon, 20 Nov 2017 10:19:23 +0000 (11:19 +0100)
committerChocobozzz <florian.bigard@gmail.com>
Mon, 27 Nov 2017 18:40:52 +0000 (19:40 +0100)
13 files changed:
client/src/app/+admin/follows/followers-list/followers-list.component.html
client/src/app/+admin/follows/following-list/following-list.component.html
client/src/app/+admin/follows/following-list/following-list.component.ts
client/src/app/+admin/follows/shared/follow.service.ts
client/src/app/+admin/users/user-list/user-list.component.ts
server/initializers/constants.ts
server/lib/activitypub/process/process-undo.ts
server/middlewares/validators/follows.ts
server/models/account/account-follow-interface.ts
server/models/account/account-follow.ts
shared/models/accounts/follow.model.ts
support/nginx/peertube
support/nginx/peertube-https

index 24d75d2b36d4e174212f0dc78ce2e207e3b8b862..549aacdf0a03fe08098423d13d9c4d39701d3726 100644 (file)
@@ -7,9 +7,9 @@
         sortField="createdAt" (onLazyLoad)="loadLazy($event)"
     >
       <p-column field="id" header="ID"></p-column>
-      <p-column field="host" header="Host"></p-column>
+      <p-column field="follower.host" header="Host"></p-column>
       <p-column field="email" header="Email"></p-column>
-      <p-column field="score" header="Score"></p-column>
+      <p-column field="follower.score" header="Score"></p-column>
       <p-column field="createdAt" header="Created date" [sortable]="true"></p-column>
     </p-dataTable>
   </div>
index fbcebfaa7b3c97a630cfc5f43a0d12582d707f07..dcc03f4a57a982555792c2a489801ae4e66ff518 100644 (file)
@@ -7,10 +7,15 @@
         sortField="createdAt" (onLazyLoad)="loadLazy($event)"
     >
       <p-column field="id" header="ID"></p-column>
-      <p-column field="host" header="Host"></p-column>
+      <p-column field="following.host" header="Host"></p-column>
       <p-column field="email" header="Email"></p-column>
-      <p-column field="score" header="Score"></p-column>
+      <p-column field="following.score" header="Score"></p-column>
       <p-column field="createdAt" header="Created date" [sortable]="true"></p-column>
+      <p-column header="Unfollow" styleClass="action-cell">
+        <ng-template pTemplate="body" let-following="rowData">
+          <span (click)="removeFollowing(following)" class="glyphicon glyphicon-remove glyphicon-black" title="Unfollow"></span>
+        </ng-template>
+      </p-column>
     </p-dataTable>
   </div>
 </div>
index a1dff1db3cef288ca4ebb99903a05e24a7bda65d..411b8f640afd23bfed11ab918b81129006d0cae3 100644 (file)
@@ -2,6 +2,7 @@ import { Component } from '@angular/core'
 import { NotificationsService } from 'angular2-notifications'
 import { SortMeta } from 'primeng/primeng'
 import { AccountFollow } from '../../../../../../shared/models/accounts/follow.model'
+import { ConfirmService } from '../../../core/confirm/confirm.service'
 import { RestPagination, RestTable } from '../../../shared'
 import { FollowService } from '../shared'
 
@@ -18,11 +19,29 @@ export class FollowingListComponent extends RestTable {
 
   constructor (
     private notificationsService: NotificationsService,
+    private confirmService: ConfirmService,
     private followService: FollowService
   ) {
     super()
   }
 
+  removeFollowing (follow: AccountFollow) {
+    this.confirmService.confirm(`Do you really want to unfollow ${follow.following.host}?`, 'Unfollow').subscribe(
+      res => {
+        if (res === false) return
+
+        this.followService.unfollow(follow).subscribe(
+          () => {
+            this.notificationsService.success('Success', `You are not following ${follow.following.host} anymore.`)
+            this.loadData()
+          },
+
+          err => this.notificationsService.error('Error', err.message)
+        )
+      }
+    )
+  }
+
   protected loadData () {
     this.followService.getFollowing(this.pagination, this.sort)
                       .subscribe(
index f66ed477df662d10fbc68e990c5c8b3148c68ad8..0bfbe8eb60e4437297dd400a87ab0a2a9f9c9fe5 100644 (file)
@@ -37,7 +37,7 @@ export class FollowService {
       .catch(res => this.restExtractor.handleError(res))
   }
 
-  follow (notEmptyHosts: String[]) {
+  follow (notEmptyHosts: string[]) {
     const body = {
       hosts: notEmptyHosts
     }
@@ -46,4 +46,10 @@ export class FollowService {
                         .map(this.restExtractor.extractDataBool)
                         .catch(res => this.restExtractor.handleError(res))
   }
+
+  unfollow (follow: AccountFollow) {
+    return this.authHttp.delete(FollowService.BASE_APPLICATION_URL + '/following/' + follow.following.id)
+      .map(this.restExtractor.extractDataBool)
+      .catch(res => this.restExtractor.handleError(res))
+  }
 }
index 6debf89beb1d177216e7a9712533c7b2b221a267..1e8e1af49ea88545d56b041ec25638242c7c09f8 100644 (file)
@@ -1,4 +1,4 @@
-import { Component, OnInit } from '@angular/core'
+import { Component } from '@angular/core'
 import { SortMeta } from 'primeng/components/common/sortmeta'
 
 import { NotificationsService } from 'angular2-notifications'
@@ -12,7 +12,7 @@ import { UserService } from '../shared'
   templateUrl: './user-list.component.html',
   styleUrls: [ './user-list.component.scss' ]
 })
-export class UserListComponent extends RestTable implements OnInit {
+export class UserListComponent extends RestTable {
   users: User[] = []
   totalRecords = 0
   rowsPerPage = 10
@@ -27,10 +27,6 @@ export class UserListComponent extends RestTable implements OnInit {
     super()
   }
 
-  ngOnInit () {
-    this.loadData()
-  }
-
   removeUser (user: User) {
     if (user.username === 'root') {
       this.notificationsService.error('Error', 'You cannot delete root.')
index 9c7c31a61d14f40a6db759ac35c24dcc20da77e0..c460439312e2630c264233d18d03a5e068301036 100644 (file)
@@ -134,7 +134,7 @@ const CONSTRAINTS_FIELDS = {
     VIEWS: { min: 0 },
     LIKES: { min: 0 },
     DISLIKES: { min: 0 },
-    FILE_SIZE: { min: 10, max: 1024 * 1024 * 1024 * 3 /* 3Go */ },
+    FILE_SIZE: { min: 10, max: 1024 * 1024 * 1024 * 10 /* 10Go */ },
     URL: { min: 3, max: 2000 } // Length
   },
   ACCOUNTS: {
index 5d09423e1046202a51e87268cc2c1de8f584a7b3..610b800fbe72b4f3a7898819fba51f2dd77f8103 100644 (file)
@@ -10,7 +10,7 @@ async function processUndoActivity (activity: ActivityUndo) {
     const following = await db.Account.loadByUrl(activityToUndo.object)
     const accountFollow = await db.AccountFollow.loadByAccountAndTarget(follower.id, following.id)
 
-    if (!accountFollow) throw new Error(`'Unknown account follow (${follower.id} -> ${following.id}.`)
+    if (!accountFollow) throw new Error(`'Unknown account follow ${follower.id} -> ${following.id}.`)
 
     await accountFollow.destroy()
 
index e22349726e581dd40fb7eaa235949b4573bcadf1..dfd6e7f03e6dc9ce80174302a5559c4a0f81e3b5 100644 (file)
@@ -1,12 +1,12 @@
 import * as express from 'express'
-import { body } from 'express-validator/check'
+import { body, param } from 'express-validator/check'
 import { isTestInstance } from '../../helpers/core-utils'
-import { isAccountIdValid } from '../../helpers/custom-validators/activitypub/account'
 import { isEachUniqueHostValid } from '../../helpers/custom-validators/servers'
 import { logger } from '../../helpers/logger'
 import { CONFIG, database as db } from '../../initializers'
 import { checkErrors } from './utils'
 import { getServerAccount } from '../../helpers/utils'
+import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc'
 
 const followValidator = [
   body('hosts').custom(isEachUniqueHostValid).withMessage('Should have an array of unique hosts'),
@@ -28,7 +28,7 @@ const followValidator = [
 ]
 
 const removeFollowingValidator = [
-  body('accountId').custom(isAccountIdValid).withMessage('Should have a valid account id'),
+  param('accountId').custom(isIdOrUUIDValid).withMessage('Should have a valid account id'),
 
   (req: express.Request, res: express.Response, next: express.NextFunction) => {
     logger.debug('Checking follow parameters', { parameters: req.body })
index 21fda98cecc3bbc681df420bbf7acea412f1806f..6f228c790a7d1f1e00ee735894e0d9e2a3f1434e 100644 (file)
@@ -1,18 +1,19 @@
-import * as Sequelize from 'sequelize'
 import * as Bluebird from 'bluebird'
-import { FollowState } from '../../../shared/models/accounts/follow.model'
+import * as Sequelize from 'sequelize'
+import { AccountFollow, FollowState } from '../../../shared/models/accounts/follow.model'
 import { ResultList } from '../../../shared/models/result-list.model'
 import { AccountInstance } from './account-interface'
 
 export namespace AccountFollowMethods {
   export type LoadByAccountAndTarget = (accountId: number, targetAccountId: number) => Bluebird<AccountFollowInstance>
 
-  export type ListFollowingForApi = (id: number, start: number, count: number, sort: string) => Bluebird< ResultList<AccountInstance> >
-  export type ListFollowersForApi = (id: number, start: number, count: number, sort: string) => Bluebird< ResultList<AccountInstance> >
+  export type ListFollowingForApi = (id: number, start: number, count: number, sort: string) => Bluebird< ResultList<AccountFollowInstance>>
+  export type ListFollowersForApi = (id: number, start: number, count: number, sort: string) => Bluebird< ResultList<AccountFollowInstance>>
 
   export type ListAcceptedFollowerUrlsForApi = (accountId: number[], start?: number, count?: number) => Promise< ResultList<string> >
   export type ListAcceptedFollowingUrlsForApi = (accountId: number[], start?: number, count?: number) => Promise< ResultList<string> >
   export type ListAcceptedFollowerSharedInboxUrls = (accountId: number[]) => Promise< ResultList<string> >
+  export type ToFormattedJSON = (this: AccountFollowInstance) => AccountFollow
 }
 
 export interface AccountFollowClass {
@@ -38,6 +39,8 @@ export interface AccountFollowInstance extends AccountFollowClass, AccountFollow
 
   AccountFollower?: AccountInstance
   AccountFollowing?: AccountInstance
+
+  toFormattedJSON: AccountFollowMethods.ToFormattedJSON
 }
 
 export interface AccountFollowModel extends AccountFollowClass, Sequelize.Model<AccountFollowInstance, AccountFollowAttributes> {}
index f00c7dcd92c22e4e06910b01d7685bb039e115f3..34ba3f8dbcb12649a74ee7b373f16a524fa72083 100644 (file)
@@ -12,6 +12,7 @@ let listFollowersForApi: AccountFollowMethods.ListFollowersForApi
 let listAcceptedFollowerUrlsForApi: AccountFollowMethods.ListAcceptedFollowerUrlsForApi
 let listAcceptedFollowingUrlsForApi: AccountFollowMethods.ListAcceptedFollowingUrlsForApi
 let listAcceptedFollowerSharedInboxUrls: AccountFollowMethods.ListAcceptedFollowerSharedInboxUrls
+let toFormattedJSON: AccountFollowMethods.ToFormattedJSON
 
 export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
   AccountFollow = sequelize.define<AccountFollowInstance, AccountFollowAttributes>('AccountFollow',
@@ -46,7 +47,10 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
     listAcceptedFollowingUrlsForApi,
     listAcceptedFollowerSharedInboxUrls
   ]
-  addMethodsToModel(AccountFollow, classMethods)
+  const instanceMethods = [
+    toFormattedJSON
+  ]
+  addMethodsToModel(AccountFollow, classMethods, instanceMethods)
 
   return AccountFollow
 }
@@ -73,6 +77,22 @@ function associate (models) {
   })
 }
 
+toFormattedJSON = function (this: AccountFollowInstance) {
+  const follower = this.AccountFollower.toFormattedJSON()
+  const following = this.AccountFollowing.toFormattedJSON()
+
+  const json = {
+    id: this.id,
+    follower,
+    following,
+    state: this.state,
+    createdAt: this.createdAt,
+    updatedAt: this.updatedAt
+  }
+
+  return json
+}
+
 loadByAccountAndTarget = function (accountId: number, targetAccountId: number) {
   const query = {
     where: {
@@ -122,7 +142,7 @@ listFollowingForApi = function (id: number, start: number, count: number, sort:
 
   return AccountFollow.findAndCountAll(query).then(({ rows, count }) => {
     return {
-      data: rows.map(r => r.AccountFollowing),
+      data: rows,
       total: count
     }
   })
@@ -154,7 +174,7 @@ listFollowersForApi = function (id: number, start: number, count: number, sort:
 
   return AccountFollow.findAndCountAll(query).then(({ rows, count }) => {
     return {
-      data: rows.map(r => r.AccountFollower),
+      data: rows,
       total: count
     }
   })
index 8094634c1ed76c758f4a6e44b3c2484d30116f6e..cdc3da5602ffc81eeee963ab03aa6ccf0c971e03 100644 (file)
@@ -1,8 +1,12 @@
+import { Account } from './account.model'
+
 export type FollowState = 'pending' | 'accepted'
 
 export interface AccountFollow {
   id: number
-  name: string
-  score?: number // Used for followers
-  host: string
+  follower: Account
+  following: Account
+  state: FollowState
+  createdAt: Date
+  updatedAt: Date
 }
index f7db3eea31f4624f72f65a118efc1748ef0ef1a4..8120738f6be2edc905bcd8450f5f3c289a1cac1f 100644 (file)
@@ -10,6 +10,9 @@ server {
 
     # For the video upload
     client_max_body_size 2G;
+    proxy_connect_timeout       600;
+    proxy_send_timeout          600;
+    proxy_read_timeout          600;
   }
 
   # Bypass PeerTube webseed route for better performances
index 1b9c40e7fc9cede42a070f9ffa48254cd5eb5639..f464428354ff4b23cb4bf736cda1aaab52e92c47 100644 (file)
@@ -23,6 +23,9 @@ server {
 
     # For the video upload
     client_max_body_size 2G;
+    proxy_connect_timeout       600;
+    proxy_send_timeout          600;
+    proxy_read_timeout          600;
   }
 
   # Bypass PeerTube webseed route for better performances