Merge pull request #1968 from syuilo/object-storage
Object storage support
This commit is contained in:
commit
7432de3d33
|
@ -53,6 +53,22 @@ remoteDriveCapacityMb: 8
|
||||||
# Users cannot see remote images when they turn off "Show media from a remote server" setting.
|
# Users cannot see remote images when they turn off "Show media from a remote server" setting.
|
||||||
preventCache: false
|
preventCache: false
|
||||||
|
|
||||||
|
drive:
|
||||||
|
storage: 'db'
|
||||||
|
|
||||||
|
# OR
|
||||||
|
|
||||||
|
# storage: 'object-storage'
|
||||||
|
# service: 'minio'
|
||||||
|
# bucket:
|
||||||
|
# prefix:
|
||||||
|
# config:
|
||||||
|
# endPoint:
|
||||||
|
# port:
|
||||||
|
# secure:
|
||||||
|
# accessKey:
|
||||||
|
# secretKey:
|
||||||
|
|
||||||
#
|
#
|
||||||
# Below settings are optional
|
# Below settings are optional
|
||||||
#
|
#
|
||||||
|
|
|
@ -9,7 +9,7 @@ const q = {
|
||||||
'metadata._user.host': {
|
'metadata._user.host': {
|
||||||
$ne: null
|
$ne: null
|
||||||
},
|
},
|
||||||
'metadata.isMetaOnly': false
|
'metadata.withoutChunks': false
|
||||||
};
|
};
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
|
@ -57,7 +57,7 @@ async function main() {
|
||||||
|
|
||||||
DriveFile.update({ _id: file._id }, {
|
DriveFile.update({ _id: file._id }, {
|
||||||
$set: {
|
$set: {
|
||||||
'metadata.isMetaOnly': true
|
'metadata.withoutChunks': true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
]).then(async () => {
|
]).then(async () => {
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
const chalk = require('chalk');
|
const chalk = require('chalk');
|
||||||
const sequential = require('promise-sequential');
|
const sequential = require('promise-sequential');
|
||||||
|
|
||||||
const { default: User } = require('../built/models/user');
|
const { default: User } = require('../../built/models/user');
|
||||||
const { default: DriveFile } = require('../built/models/drive-file');
|
const { default: DriveFile } = require('../../built/models/drive-file');
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const promiseGens = [];
|
const promiseGens = [];
|
|
@ -3,8 +3,8 @@
|
||||||
const chalk = require('chalk');
|
const chalk = require('chalk');
|
||||||
const sequential = require('promise-sequential');
|
const sequential = require('promise-sequential');
|
||||||
|
|
||||||
const { default: User } = require('../built/models/user');
|
const { default: User } = require('../../built/models/user');
|
||||||
const { default: DriveFile } = require('../built/models/drive-file');
|
const { default: DriveFile } = require('../../built/models/drive-file');
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const promiseGens = [];
|
const promiseGens = [];
|
|
@ -0,0 +1,10 @@
|
||||||
|
const { default: DriveFile } = require('../../built/models/drive-file');
|
||||||
|
|
||||||
|
DriveFile.update({}, {
|
||||||
|
$rename: {
|
||||||
|
'metadata.url': 'metadata.src',
|
||||||
|
'metadata.isMetaOnly': 'metadata.withoutChunks',
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
multi: true
|
||||||
|
});
|
|
@ -1,11 +0,0 @@
|
||||||
Misskeyの破壊的変更に対応するいくつかのスニペットがあります。
|
|
||||||
MongoDBシェルで実行する必要のあるものとnodeで直接実行する必要のあるものがあります。
|
|
||||||
ファイル名が `shell.` から始まるものは前者、 `node.` から始まるものは後者です。
|
|
||||||
|
|
||||||
MongoDBシェルで実行する場合、`use`でデータベースを選択しておく必要があります。
|
|
||||||
|
|
||||||
nodeで実行するいくつかのスニペットは、並列処理させる数を引数で設定できるものがあります。
|
|
||||||
処理中にエラーで落ちる場合は、メモリが足りていない可能性があるので、少ない数に設定してみてください。
|
|
||||||
※デフォルトは`5`です。
|
|
||||||
|
|
||||||
ファイルを作成する際は `../init-migration-file.sh -t _type_ -n _name_` を実行すると _type_._unixtime_._name_.js が生成されます
|
|
|
@ -1,37 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
usage() {
|
|
||||||
echo "$0 [-t type] [-n name]"
|
|
||||||
echo " type: [node | shell]"
|
|
||||||
echo " name: if no present, set untitled"
|
|
||||||
exit 0
|
|
||||||
}
|
|
||||||
|
|
||||||
while getopts :t:n:h OPT
|
|
||||||
do
|
|
||||||
case $OPT in
|
|
||||||
t) type=$OPTARG
|
|
||||||
;;
|
|
||||||
n) name=$OPTARG
|
|
||||||
;;
|
|
||||||
h) usage
|
|
||||||
;;
|
|
||||||
\?) usage
|
|
||||||
;;
|
|
||||||
:) usage
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ "$type" = "" ]
|
|
||||||
then
|
|
||||||
echo "no type present!!!"
|
|
||||||
usage
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$name" = "" ]
|
|
||||||
then
|
|
||||||
name="untitled"
|
|
||||||
fi
|
|
||||||
|
|
||||||
touch "$(realpath $(dirname $BASH_SOURCE))/migration/$type.$(date +%s).$name.js"
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "misskey",
|
"name": "misskey",
|
||||||
"author": "syuilo <i@syuilo.com>",
|
"author": "syuilo <i@syuilo.com>",
|
||||||
"version": "4.27.0",
|
"version": "5.0.0",
|
||||||
"clientVersion": "1.0.7487",
|
"clientVersion": "1.0.7487",
|
||||||
"codename": "nighthike",
|
"codename": "nighthike",
|
||||||
"main": "./built/index.js",
|
"main": "./built/index.js",
|
||||||
|
@ -57,6 +57,7 @@
|
||||||
"@types/koa-views": "2.0.3",
|
"@types/koa-views": "2.0.3",
|
||||||
"@types/koa__cors": "2.2.2",
|
"@types/koa__cors": "2.2.2",
|
||||||
"@types/kue": "0.11.9",
|
"@types/kue": "0.11.9",
|
||||||
|
"@types/minio": "6.0.2",
|
||||||
"@types/mkdirp": "0.5.2",
|
"@types/mkdirp": "0.5.2",
|
||||||
"@types/mocha": "5.2.3",
|
"@types/mocha": "5.2.3",
|
||||||
"@types/mongodb": "3.1.2",
|
"@types/mongodb": "3.1.2",
|
||||||
|
@ -147,6 +148,7 @@
|
||||||
"kue": "0.11.6",
|
"kue": "0.11.6",
|
||||||
"loader-utils": "1.1.0",
|
"loader-utils": "1.1.0",
|
||||||
"mecab-async": "0.1.2",
|
"mecab-async": "0.1.2",
|
||||||
|
"minio": "6.0.0",
|
||||||
"mkdirp": "0.5.1",
|
"mkdirp": "0.5.1",
|
||||||
"mocha": "5.2.0",
|
"mocha": "5.2.0",
|
||||||
"moji": "0.5.1",
|
"moji": "0.5.1",
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<div class="form">
|
<div class="form">
|
||||||
<header>
|
<header>
|
||||||
<h1><i>{{ app.name }}</i>があなたのアカウントにアクセスすることを<b>許可</b>しますか?</h1>
|
<h1><i>{{ app.name }}</i>があなたのアカウントにアクセスすることを<b>許可</b>しますか?</h1>
|
||||||
<img :src="`${app.iconUrl}?thumbnail&size=64`"/>
|
<img :src="app.iconUrl"/>
|
||||||
</header>
|
</header>
|
||||||
<div class="app">
|
<div class="app">
|
||||||
<section>
|
<section>
|
||||||
|
|
|
@ -17,21 +17,21 @@ export default function(type, data): Notification {
|
||||||
return {
|
return {
|
||||||
title: 'ファイルがアップロードされました',
|
title: 'ファイルがアップロードされました',
|
||||||
body: data.name,
|
body: data.name,
|
||||||
icon: data.url + '?thumbnail&size=64'
|
icon: data.url
|
||||||
};
|
};
|
||||||
|
|
||||||
case 'unread_messaging_message':
|
case 'unread_messaging_message':
|
||||||
return {
|
return {
|
||||||
title: `${getUserName(data.user)}さんからメッセージ:`,
|
title: `${getUserName(data.user)}さんからメッセージ:`,
|
||||||
body: data.text, // TODO: getMessagingMessageSummary(data),
|
body: data.text, // TODO: getMessagingMessageSummary(data),
|
||||||
icon: data.user.avatarUrl + '?thumbnail&size=64'
|
icon: data.user.avatarUrl
|
||||||
};
|
};
|
||||||
|
|
||||||
case 'reversi_invited':
|
case 'reversi_invited':
|
||||||
return {
|
return {
|
||||||
title: '対局への招待があります',
|
title: '対局への招待があります',
|
||||||
body: `${getUserName(data.parent)}さんから`,
|
body: `${getUserName(data.parent)}さんから`,
|
||||||
icon: data.parent.avatarUrl + '?thumbnail&size=64'
|
icon: data.parent.avatarUrl
|
||||||
};
|
};
|
||||||
|
|
||||||
case 'notification':
|
case 'notification':
|
||||||
|
@ -40,28 +40,28 @@ export default function(type, data): Notification {
|
||||||
return {
|
return {
|
||||||
title: `${getUserName(data.user)}さんから:`,
|
title: `${getUserName(data.user)}さんから:`,
|
||||||
body: getNoteSummary(data),
|
body: getNoteSummary(data),
|
||||||
icon: data.user.avatarUrl + '?thumbnail&size=64'
|
icon: data.user.avatarUrl
|
||||||
};
|
};
|
||||||
|
|
||||||
case 'reply':
|
case 'reply':
|
||||||
return {
|
return {
|
||||||
title: `${getUserName(data.user)}さんから返信:`,
|
title: `${getUserName(data.user)}さんから返信:`,
|
||||||
body: getNoteSummary(data),
|
body: getNoteSummary(data),
|
||||||
icon: data.user.avatarUrl + '?thumbnail&size=64'
|
icon: data.user.avatarUrl
|
||||||
};
|
};
|
||||||
|
|
||||||
case 'quote':
|
case 'quote':
|
||||||
return {
|
return {
|
||||||
title: `${getUserName(data.user)}さんが引用:`,
|
title: `${getUserName(data.user)}さんが引用:`,
|
||||||
body: getNoteSummary(data),
|
body: getNoteSummary(data),
|
||||||
icon: data.user.avatarUrl + '?thumbnail&size=64'
|
icon: data.user.avatarUrl
|
||||||
};
|
};
|
||||||
|
|
||||||
case 'reaction':
|
case 'reaction':
|
||||||
return {
|
return {
|
||||||
title: `${getUserName(data.user)}: ${getReactionEmoji(data.reaction)}:`,
|
title: `${getUserName(data.user)}: ${getReactionEmoji(data.reaction)}:`,
|
||||||
body: getNoteSummary(data.note),
|
body: getNoteSummary(data.note),
|
||||||
icon: data.user.avatarUrl + '?thumbnail&size=64'
|
icon: data.user.avatarUrl
|
||||||
};
|
};
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<div class="mk-autocomplete" @contextmenu.prevent="() => {}">
|
<div class="mk-autocomplete" @contextmenu.prevent="() => {}">
|
||||||
<ol class="users" ref="suggests" v-if="users.length > 0">
|
<ol class="users" ref="suggests" v-if="users.length > 0">
|
||||||
<li v-for="user in users" @click="complete(type, user)" @keydown="onKeydown" tabindex="-1">
|
<li v-for="user in users" @click="complete(type, user)" @keydown="onKeydown" tabindex="-1">
|
||||||
<img class="avatar" :src="`${user.avatarUrl}?thumbnail&size=32`" alt=""/>
|
<img class="avatar" :src="user.avatarUrl" alt=""/>
|
||||||
<span class="name">{{ user | userName }}</span>
|
<span class="name">{{ user | userName }}</span>
|
||||||
<span class="username">@{{ user | acct }}</span>
|
<span class="username">@{{ user | acct }}</span>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -31,7 +31,7 @@ export default Vue.extend({
|
||||||
: this.user.avatarColor && this.user.avatarColor.length == 3
|
: this.user.avatarColor && this.user.avatarColor.length == 3
|
||||||
? `rgb(${ this.user.avatarColor.join(',') })`
|
? `rgb(${ this.user.avatarColor.join(',') })`
|
||||||
: null,
|
: null,
|
||||||
backgroundImage: this.lightmode ? null : `url(${ this.user.avatarUrl }?thumbnail)`,
|
backgroundImage: this.lightmode ? null : `url(${ this.user.avatarUrl })`,
|
||||||
borderRadius: this.$store.state.settings.circleIcons ? '100%' : null
|
borderRadius: this.$store.state.settings.circleIcons ? '100%' : null
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,8 +26,8 @@
|
||||||
:class="{ empty: stone == null, none: o.map[i] == 'null', isEnded: game.isEnded, myTurn: !game.isEnded && isMyTurn, can: turnUser ? o.canPut(turnUser.id == blackUser.id, i) : null, prev: o.prevPos == i }"
|
:class="{ empty: stone == null, none: o.map[i] == 'null', isEnded: game.isEnded, myTurn: !game.isEnded && isMyTurn, can: turnUser ? o.canPut(turnUser.id == blackUser.id, i) : null, prev: o.prevPos == i }"
|
||||||
@click="set(i)"
|
@click="set(i)"
|
||||||
:title="`${String.fromCharCode(65 + o.transformPosToXy(i)[0])}${o.transformPosToXy(i)[1] + 1}`">
|
:title="`${String.fromCharCode(65 + o.transformPosToXy(i)[0])}${o.transformPosToXy(i)[1] + 1}`">
|
||||||
<img v-if="stone === true" :src="`${blackUser.avatarUrl}?thumbnail&size=128`" alt="">
|
<img v-if="stone === true" :src="blackUser.avatarUrl" alt="">
|
||||||
<img v-if="stone === false" :src="`${whiteUser.avatarUrl}?thumbnail&size=128`" alt="">
|
<img v-if="stone === false" :src="whiteUser.avatarUrl" alt="">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="labels-y" v-if="this.$store.state.settings.reversiBoardLabels">
|
<div class="labels-y" v-if="this.$store.state.settings.reversiBoardLabels">
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
<p :class="$style.fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p>
|
<p :class="$style.fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p>
|
||||||
<div :class="$style.stream" v-if="!fetching && images.length > 0">
|
<div :class="$style.stream" v-if="!fetching && images.length > 0">
|
||||||
<div v-for="image in images" :class="$style.img" :style="`background-image: url(${image.url}?thumbnail&size=256)`"></div>
|
<div v-for="image in images" :class="$style.img" :style="`background-image: url(${image.url})`"></div>
|
||||||
</div>
|
</div>
|
||||||
<p :class="$style.empty" v-if="!fetching && images.length == 0">%i18n:@no-photos%</p>
|
<p :class="$style.empty" v-if="!fetching && images.length == 0">%i18n:@no-photos%</p>
|
||||||
</mk-widget-container>
|
</mk-widget-container>
|
||||||
|
|
|
@ -72,7 +72,7 @@ export default define({
|
||||||
if (this.images.length == 0) return;
|
if (this.images.length == 0) return;
|
||||||
|
|
||||||
const index = Math.floor(Math.random() * this.images.length);
|
const index = Math.floor(Math.random() * this.images.length);
|
||||||
const img = `url(${ this.images[index].url }?thumbnail&size=1024)`;
|
const img = `url(${ this.images[index].url })`;
|
||||||
|
|
||||||
(this.$refs.slideB as any).style.backgroundImage = img;
|
(this.$refs.slideB as any).style.backgroundImage = img;
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
<p>%i18n:@banner%</p>
|
<p>%i18n:@banner%</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="thumbnail" ref="thumbnail" :style="`background-color: ${ background }`">
|
<div class="thumbnail" ref="thumbnail" :style="`background-color: ${ background }`">
|
||||||
<img :src="`${file.url}?thumbnail&size=128`" alt="" @load="onThumbnailLoaded"/>
|
<img :src="file.url" alt="" @load="onThumbnailLoaded"/>
|
||||||
</div>
|
</div>
|
||||||
<p class="name">
|
<p class="name">
|
||||||
<span>{{ file.name.lastIndexOf('.') != -1 ? file.name.substr(0, file.name.lastIndexOf('.')) : file.name }}</span>
|
<span>{{ file.name.lastIndexOf('.') != -1 ? file.name.substr(0, file.name.lastIndexOf('.')) : file.name }}</span>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<mk-window width="400px" height="550px" @closed="$destroy">
|
<mk-window width="400px" height="550px" @closed="$destroy">
|
||||||
<span slot="header" :class="$style.header">
|
<span slot="header" :class="$style.header">
|
||||||
<img :src="`${user.avatarUrl}?thumbnail&size=64`" alt=""/>{{ '%i18n:@followers%'.replace('{}', name) }}
|
<img :src="user.avatarUrl" alt=""/>{{ '%i18n:@followers%'.replace('{}', name) }}
|
||||||
</span>
|
</span>
|
||||||
<mk-followers :user="user"/>
|
<mk-followers :user="user"/>
|
||||||
</mk-window>
|
</mk-window>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<mk-window width="400px" height="550px" @closed="$destroy">
|
<mk-window width="400px" height="550px" @closed="$destroy">
|
||||||
<span slot="header" :class="$style.header">
|
<span slot="header" :class="$style.header">
|
||||||
<img :src="`${user.avatarUrl}?thumbnail&size=64`" alt=""/>{{ '%i18n:@following%'.replace('{}', name) }}
|
<img :src="user.avatarUrl" alt=""/>{{ '%i18n:@following%'.replace('{}', name) }}
|
||||||
</span>
|
</span>
|
||||||
<mk-following :user="user"/>
|
<mk-following :user="user"/>
|
||||||
</mk-window>
|
</mk-window>
|
||||||
|
|
|
@ -37,7 +37,7 @@ export default Vue.extend({
|
||||||
style(): any {
|
style(): any {
|
||||||
return {
|
return {
|
||||||
'background-color': this.image.properties.avgColor && this.image.properties.avgColor.length == 3 ? `rgb(${this.image.properties.avgColor.join(',')})` : 'transparent',
|
'background-color': this.image.properties.avgColor && this.image.properties.avgColor.length == 3 ? `rgb(${this.image.properties.avgColor.join(',')})` : 'transparent',
|
||||||
'background-image': this.raw ? `url(${this.image.url})` : `url(${this.image.url}?thumbnail&size=512)`
|
'background-image': this.raw ? `url(${this.image.url})` : `url(${this.image.url})`
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -45,7 +45,7 @@ export default Vue.extend({
|
||||||
computed: {
|
computed: {
|
||||||
imageStyle(): any {
|
imageStyle(): any {
|
||||||
return {
|
return {
|
||||||
'background-image': `url(${this.video.url}?thumbnail&size=512)`
|
'background-image': `url(${this.video.url})`
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
<div class="medias" :class="{ with: poll }" v-show="files.length != 0">
|
<div class="medias" :class="{ with: poll }" v-show="files.length != 0">
|
||||||
<x-draggable :list="files" :options="{ animation: 150 }">
|
<x-draggable :list="files" :options="{ animation: 150 }">
|
||||||
<div v-for="file in files" :key="file.id">
|
<div v-for="file in files" :key="file.id">
|
||||||
<div class="img" :style="{ backgroundImage: `url(${file.url}?thumbnail&size=64)` }" :title="file.name"></div>
|
<div class="img" :style="{ backgroundImage: `url(${file.url})` }" :title="file.name"></div>
|
||||||
<img class="remove" @click="detachMedia(file.id)" src="/assets/desktop/remove.png" title="%i18n:@attach-cancel%" alt=""/>
|
<img class="remove" @click="detachMedia(file.id)" src="/assets/desktop/remove.png" title="%i18n:@attach-cancel%" alt=""/>
|
||||||
</div>
|
</div>
|
||||||
</x-draggable>
|
</x-draggable>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<div class="profile">
|
<div class="profile">
|
||||||
<label class="avatar ui from group">
|
<label class="avatar ui from group">
|
||||||
<p>%i18n:@avatar%</p>
|
<p>%i18n:@avatar%</p>
|
||||||
<img class="avatar" :src="`${$store.state.i.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
|
<img class="avatar" :src="$store.state.i.avatarUrl" alt="avatar"/>
|
||||||
<button class="ui" @click="updateAvatar">%i18n:@choice-avatar%</button>
|
<button class="ui" @click="updateAvatar">%i18n:@choice-avatar%</button>
|
||||||
</label>
|
</label>
|
||||||
<label class="ui from group">
|
<label class="ui from group">
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="mk-user-preview">
|
<div class="mk-user-preview">
|
||||||
<template v-if="u != null">
|
<template v-if="u != null">
|
||||||
<div class="banner" :style="u.bannerUrl ? `background-image: url(${u.bannerUrl}?thumbnail&size=512)` : ''"></div>
|
<div class="banner" :style="u.bannerUrl ? `background-image: url(${u.bannerUrl})` : ''"></div>
|
||||||
<mk-avatar class="avatar" :user="u" :disable-preview="true"/>
|
<mk-avatar class="avatar" :user="u" :disable-preview="true"/>
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<router-link class="name" :to="u | userPage">{{ u | userName }}</router-link>
|
<router-link class="name" :to="u | userPage">{{ u | userName }}</router-link>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<p class="initializing" v-if="fetching">%fa:spinner .pulse .fw%%i18n:@loading%<mk-ellipsis/></p>
|
<p class="initializing" v-if="fetching">%fa:spinner .pulse .fw%%i18n:@loading%<mk-ellipsis/></p>
|
||||||
<div v-if="!fetching && users.length > 0">
|
<div v-if="!fetching && users.length > 0">
|
||||||
<router-link v-for="user in users" :to="user | userPage" :key="user.id">
|
<router-link v-for="user in users" :to="user | userPage" :key="user.id">
|
||||||
<img :src="`${user.avatarUrl}?thumbnail&size=64`" :alt="user | userName" v-user-preview="user.id"/>
|
<img :src="user.avatarUrl" :alt="user | userName" v-user-preview="user.id"/>
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
<p class="empty" v-if="!fetching && users.length == 0">%i18n:@no-users%</p>
|
<p class="empty" v-if="!fetching && users.length == 0">%i18n:@no-users%</p>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<p class="initializing" v-if="fetching">%fa:spinner .pulse .fw%%i18n:@loading%<mk-ellipsis/></p>
|
<p class="initializing" v-if="fetching">%fa:spinner .pulse .fw%%i18n:@loading%<mk-ellipsis/></p>
|
||||||
<div class="stream" v-if="!fetching && images.length > 0">
|
<div class="stream" v-if="!fetching && images.length > 0">
|
||||||
<div v-for="image in images" class="img"
|
<div v-for="image in images" class="img"
|
||||||
:style="`background-image: url(${image.url}?thumbnail&size=256)`"
|
:style="`background-image: url(${image.url})`"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
<p class="empty" v-if="!fetching && images.length == 0">%i18n:@no-photos%</p>
|
<p class="empty" v-if="!fetching && images.length == 0">%i18n:@no-photos%</p>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
:data-melt="props.design == 2"
|
:data-melt="props.design == 2"
|
||||||
>
|
>
|
||||||
<div class="banner"
|
<div class="banner"
|
||||||
:style="$store.state.i.bannerUrl ? `background-image: url(${$store.state.i.bannerUrl}?thumbnail&size=256)` : ''"
|
:style="$store.state.i.bannerUrl ? `background-image: url(${$store.state.i.bannerUrl})` : ''"
|
||||||
title="%i18n:@update-banner%"
|
title="%i18n:@update-banner%"
|
||||||
@click="os.apis.updateBanner"
|
@click="os.apis.updateBanner"
|
||||||
></div>
|
></div>
|
||||||
|
|
|
@ -43,7 +43,7 @@ export default Vue.extend({
|
||||||
thumbnail(): any {
|
thumbnail(): any {
|
||||||
return {
|
return {
|
||||||
'background-color': this.file.properties.avgColor && this.file.properties.avgColor.length == 3 ? `rgb(${this.file.properties.avgColor.join(',')})` : 'transparent',
|
'background-color': this.file.properties.avgColor && this.file.properties.avgColor.length == 3 ? `rgb(${this.file.properties.avgColor.join(',')})` : 'transparent',
|
||||||
'background-image': `url(${this.file.url}?thumbnail&size=128)`
|
'background-image': `url(${this.file.url})`
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -27,7 +27,7 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
style(): any {
|
style(): any {
|
||||||
let url = `url(${this.image.url}?thumbnail)`;
|
let url = `url(${this.image.url})`;
|
||||||
|
|
||||||
if (this.$store.state.device.loadRemoteMedia || this.$store.state.device.lightmode) {
|
if (this.$store.state.device.loadRemoteMedia || this.$store.state.device.lightmode) {
|
||||||
url = null;
|
url = null;
|
||||||
|
|
|
@ -30,7 +30,7 @@ export default Vue.extend({
|
||||||
computed: {
|
computed: {
|
||||||
imageStyle(): any {
|
imageStyle(): any {
|
||||||
return {
|
return {
|
||||||
'background-image': `url(${this.video.url}?thumbnail&size=512)`
|
'background-image': `url(${this.video.url})`
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
},})
|
},})
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<div class="mk-note-card">
|
<div class="mk-note-card">
|
||||||
<a :href="note | notePage">
|
<a :href="note | notePage">
|
||||||
<header>
|
<header>
|
||||||
<img :src="`${note.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/><h3>{{ note.user | userName }}</h3>
|
<img :src="note.user.avatarUrl" alt="avatar"/><h3>{{ note.user | userName }}</h3>
|
||||||
</header>
|
</header>
|
||||||
<div>
|
<div>
|
||||||
{{ text }}
|
{{ text }}
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
<div class="attaches" v-show="files.length != 0">
|
<div class="attaches" v-show="files.length != 0">
|
||||||
<x-draggable class="files" :list="files" :options="{ animation: 150 }">
|
<x-draggable class="files" :list="files" :options="{ animation: 150 }">
|
||||||
<div class="file" v-for="file in files" :key="file.id">
|
<div class="file" v-for="file in files" :key="file.id">
|
||||||
<div class="img" :style="`background-image: url(${file.url}?thumbnail&size=128)`" @click="detachMedia(file)"></div>
|
<div class="img" :style="`background-image: url(${file.url})`" @click="detachMedia(file)"></div>
|
||||||
</div>
|
</div>
|
||||||
</x-draggable>
|
</x-draggable>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
<transition name="nav">
|
<transition name="nav">
|
||||||
<div class="body" v-if="isOpen">
|
<div class="body" v-if="isOpen">
|
||||||
<router-link class="me" v-if="$store.getters.isSignedIn" :to="`/@${$store.state.i.username}`">
|
<router-link class="me" v-if="$store.getters.isSignedIn" :to="`/@${$store.state.i.username}`">
|
||||||
<img class="avatar" :src="`${$store.state.i.avatarUrl}?thumbnail&size=128`" alt="avatar"/>
|
<img class="avatar" :src="$store.state.i.avatarUrl" alt="avatar"/>
|
||||||
<p class="name">{{ $store.state.i | userName }}</p>
|
<p class="name">{{ $store.state.i | userName }}</p>
|
||||||
</router-link>
|
</router-link>
|
||||||
<div class="links">
|
<div class="links">
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="mk-user-card">
|
<div class="mk-user-card">
|
||||||
<header :style="user.bannerUrl ? `background-image: url(${user.bannerUrl}?thumbnail&size=1024)` : ''">
|
<header :style="user.bannerUrl ? `background-image: url(${user.bannerUrl})` : ''">
|
||||||
<mk-avatar class="avatar" :user="user"/>
|
<mk-avatar class="avatar" :user="user"/>
|
||||||
</header>
|
</header>
|
||||||
<a class="name" :href="user | userPage" target="_blank">{{ user | userName }}</a>
|
<a class="name" :href="user | userPage" target="_blank">{{ user | userName }}</a>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<mk-ui>
|
<mk-ui>
|
||||||
<template slot="header" v-if="!fetching">
|
<template slot="header" v-if="!fetching">
|
||||||
<img :src="`${user.avatarUrl}?thumbnail&size=64`" alt="">
|
<img :src="user.avatarUrl" alt="">
|
||||||
{{ '%i18n:@followers-of%'.replace('{}', name) }}
|
{{ '%i18n:@followers-of%'.replace('{}', name) }}
|
||||||
</template>
|
</template>
|
||||||
<mk-users-list
|
<mk-users-list
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<mk-ui>
|
<mk-ui>
|
||||||
<template slot="header" v-if="!fetching">
|
<template slot="header" v-if="!fetching">
|
||||||
<img :src="`${user.avatarUrl}?thumbnail&size=64`" alt="">
|
<img :src="user.avatarUrl" alt="">
|
||||||
{{ '%i18n:@following-of%'.replace('{}', name) }}
|
{{ '%i18n:@following-of%'.replace('{}', name) }}
|
||||||
</template>
|
</template>
|
||||||
<mk-users-list
|
<mk-users-list
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<mk-ui>
|
<mk-ui>
|
||||||
<template slot="header" v-if="!fetching"><img :src="`${user.avatarUrl}?thumbnail&size=64`" alt="">{{ user | userName }}</template>
|
<template slot="header" v-if="!fetching"><img :src="user.avatarUrl" alt="">{{ user | userName }}</template>
|
||||||
<main v-if="!fetching" :data-darkmode="$store.state.device.darkmode">
|
<main v-if="!fetching" :data-darkmode="$store.state.device.darkmode">
|
||||||
<div class="is-suspended" v-if="user.isSuspended"><p>%fa:exclamation-triangle% %i18n:@is-suspended%</p></div>
|
<div class="is-suspended" v-if="user.isSuspended"><p>%fa:exclamation-triangle% %i18n:@is-suspended%</p></div>
|
||||||
<div class="is-remote" v-if="user.host != null"><p>%fa:exclamation-triangle% %i18n:@is-remote%<a :href="user.url || user.uri" target="_blank">%i18n:@view-remote%</a></p></div>
|
<div class="is-remote" v-if="user.host != null"><p>%fa:exclamation-triangle% %i18n:@is-remote%<a :href="user.url || user.uri" target="_blank">%i18n:@view-remote%</a></p></div>
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<p class="initializing" v-if="fetching">%fa:spinner .pulse .fw%%i18n:@loading%<mk-ellipsis/></p>
|
<p class="initializing" v-if="fetching">%fa:spinner .pulse .fw%%i18n:@loading%<mk-ellipsis/></p>
|
||||||
<div v-if="!fetching && users.length > 0">
|
<div v-if="!fetching && users.length > 0">
|
||||||
<a v-for="user in users" :key="user.id" :href="user | userPage">
|
<a v-for="user in users" :key="user.id" :href="user | userPage">
|
||||||
<img :src="`${user.avatarUrl}?thumbnail&size=64`" :alt="user | userName"/>
|
<img :src="user.avatarUrl" :alt="user | userName"/>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<p class="empty" v-if="!fetching && users.length == 0">%i18n:@no-users%</p>
|
<p class="empty" v-if="!fetching && users.length == 0">%i18n:@no-users%</p>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<div class="stream" v-if="!fetching && images.length > 0">
|
<div class="stream" v-if="!fetching && images.length > 0">
|
||||||
<a v-for="image in images"
|
<a v-for="image in images"
|
||||||
class="img"
|
class="img"
|
||||||
:style="`background-image: url(${image.media.url}?thumbnail&size=256)`"
|
:style="`background-image: url(${image.media.url})`"
|
||||||
:href="image.note | notePage"
|
:href="image.note | notePage"
|
||||||
></a>
|
></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,10 +2,10 @@
|
||||||
<div class="mkw-profile">
|
<div class="mkw-profile">
|
||||||
<mk-widget-container>
|
<mk-widget-container>
|
||||||
<div :class="$style.banner"
|
<div :class="$style.banner"
|
||||||
:style="$store.state.i.bannerUrl ? `background-image: url(${$store.state.i.bannerUrl}?thumbnail&size=256)` : ''"
|
:style="$store.state.i.bannerUrl ? `background-image: url(${$store.state.i.bannerUrl})` : ''"
|
||||||
></div>
|
></div>
|
||||||
<img :class="$style.avatar"
|
<img :class="$style.avatar"
|
||||||
:src="`${$store.state.i.avatarUrl}?thumbnail&size=96`"
|
:src="$store.state.i.avatarUrl"
|
||||||
alt="avatar"
|
alt="avatar"
|
||||||
/>
|
/>
|
||||||
<router-link :class="$style.name" :to="$store.state.i | userPage">{{ $store.state.i | userName }}</router-link>
|
<router-link :class="$style.name" :to="$store.state.i | userPage">{{ $store.state.i | userName }}</router-link>
|
||||||
|
|
|
@ -49,6 +49,14 @@ export type Source = {
|
||||||
remoteDriveCapacityMb: number;
|
remoteDriveCapacityMb: number;
|
||||||
preventCacheRemoteFiles: boolean;
|
preventCacheRemoteFiles: boolean;
|
||||||
|
|
||||||
|
drive?: {
|
||||||
|
storage: string;
|
||||||
|
bucket: string;
|
||||||
|
prefix: string;
|
||||||
|
service?: string;
|
||||||
|
config?: any;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ゴーストアカウントのID
|
* ゴーストアカウントのID
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -31,8 +31,11 @@ export type IMetadata = {
|
||||||
comment: string;
|
comment: string;
|
||||||
uri?: string;
|
uri?: string;
|
||||||
url?: string;
|
url?: string;
|
||||||
|
src?: string;
|
||||||
deletedAt?: Date;
|
deletedAt?: Date;
|
||||||
isMetaOnly?: boolean;
|
withoutChunks?: boolean;
|
||||||
|
storage?: string;
|
||||||
|
storageProps?: any;
|
||||||
isSensitive?: boolean;
|
isSensitive?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -155,9 +158,9 @@ export const pack = (
|
||||||
|
|
||||||
_target = Object.assign(_target, _file.metadata);
|
_target = Object.assign(_target, _file.metadata);
|
||||||
|
|
||||||
|
_target.url = _file.metadata.url ? _file.metadata.url : `${config.drive_url}/${_target.id}/${encodeURIComponent(_target.name)}`;
|
||||||
_target.src = _file.metadata.url;
|
_target.src = _file.metadata.url;
|
||||||
_target.url = _file.metadata.isMetaOnly ? _file.metadata.url : `${config.drive_url}/${_target.id}/${encodeURIComponent(_target.name)}`;
|
_target.isRemote = _file.metadata.withoutChunks;
|
||||||
_target.isRemote = _file.metadata.isMetaOnly;
|
|
||||||
|
|
||||||
if (_target.properties == null) _target.properties = {};
|
if (_target.properties == null) _target.properties = {};
|
||||||
|
|
||||||
|
|
|
@ -152,8 +152,8 @@ export async function createPerson(value: any, resolver?: Resolver): Promise<IUs
|
||||||
|
|
||||||
const avatarId = avatar ? avatar._id : null;
|
const avatarId = avatar ? avatar._id : null;
|
||||||
const bannerId = banner ? banner._id : null;
|
const bannerId = banner ? banner._id : null;
|
||||||
const avatarUrl = avatar && avatar.metadata.isMetaOnly ? avatar.metadata.url : null;
|
const avatarUrl = avatar && avatar.metadata.url ? avatar.metadata.url : null;
|
||||||
const bannerUrl = banner && banner.metadata.isMetaOnly ? banner.metadata.url : null;
|
const bannerUrl = banner && banner.metadata.url ? banner.metadata.url : null;
|
||||||
|
|
||||||
await User.update({ _id: user._id }, {
|
await User.update({ _id: user._id }, {
|
||||||
$set: {
|
$set: {
|
||||||
|
@ -243,8 +243,8 @@ export async function updatePerson(value: string | IObject, resolver?: Resolver)
|
||||||
sharedInbox: person.sharedInbox,
|
sharedInbox: person.sharedInbox,
|
||||||
avatarId: avatar ? avatar._id : null,
|
avatarId: avatar ? avatar._id : null,
|
||||||
bannerId: banner ? banner._id : null,
|
bannerId: banner ? banner._id : null,
|
||||||
avatarUrl: avatar && avatar.metadata.isMetaOnly ? avatar.metadata.url : null,
|
avatarUrl: avatar && avatar.metadata.url ? avatar.metadata.url : null,
|
||||||
bannerUrl: banner && banner.metadata.isMetaOnly ? banner.metadata.url : null,
|
bannerUrl: banner && banner.metadata.url ? banner.metadata.url : null,
|
||||||
description: htmlToMFM(person.summary),
|
description: htmlToMFM(person.summary),
|
||||||
followersCount,
|
followersCount,
|
||||||
followingCount,
|
followingCount,
|
||||||
|
|
|
@ -37,7 +37,7 @@ export default async function(ctx: Koa.Context) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (file.metadata.isMetaOnly) {
|
if (file.metadata.withoutChunks) {
|
||||||
ctx.status = 204;
|
ctx.status = 204;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,14 +8,14 @@ import * as _gm from 'gm';
|
||||||
import * as debug from 'debug';
|
import * as debug from 'debug';
|
||||||
import fileType = require('file-type');
|
import fileType = require('file-type');
|
||||||
const prominence = require('prominence');
|
const prominence = require('prominence');
|
||||||
|
import * as Minio from 'minio';
|
||||||
|
import * as uuid from 'uuid';
|
||||||
|
|
||||||
import DriveFile, { IMetadata, getDriveFileBucket, IDriveFile } from '../../models/drive-file';
|
import DriveFile, { IMetadata, getDriveFileBucket, IDriveFile } from '../../models/drive-file';
|
||||||
import DriveFolder from '../../models/drive-folder';
|
import DriveFolder from '../../models/drive-folder';
|
||||||
import { pack } from '../../models/drive-file';
|
import { pack } from '../../models/drive-file';
|
||||||
import event, { publishDriveStream } from '../../stream';
|
import event, { publishDriveStream } from '../../stream';
|
||||||
import { isLocalUser, IUser, IRemoteUser } from '../../models/user';
|
import { isLocalUser, IUser, IRemoteUser } from '../../models/user';
|
||||||
import { getDriveFileThumbnailBucket } from '../../models/drive-file-thumbnail';
|
|
||||||
import genThumbnail from '../../drive/gen-thumbnail';
|
|
||||||
import delFile from './delete-file';
|
import delFile from './delete-file';
|
||||||
import config from '../../config';
|
import config from '../../config';
|
||||||
|
|
||||||
|
@ -25,28 +25,47 @@ const gm = _gm.subClass({
|
||||||
|
|
||||||
const log = debug('misskey:drive:add-file');
|
const log = debug('misskey:drive:add-file');
|
||||||
|
|
||||||
const writeChunks = (name: string, readable: stream.Readable, type: string, metadata: any) =>
|
async function save(readable: stream.Readable, name: string, type: string, hash: string, size: number, metadata: any): Promise<IDriveFile> {
|
||||||
getDriveFileBucket()
|
if (config.drive && config.drive.storage == 'object-storage') {
|
||||||
.then(bucket => new Promise((resolve, reject) => {
|
if (config.drive.service == 'minio') {
|
||||||
|
|
||||||
|
const minio = new Minio.Client(config.drive.config);
|
||||||
|
const id = uuid.v4();
|
||||||
|
const obj = `${config.drive.prefix}/${id}`;
|
||||||
|
await minio.putObject(config.drive.bucket, obj, readable);
|
||||||
|
|
||||||
|
Object.assign(metadata, {
|
||||||
|
withoutChunks: true,
|
||||||
|
storage: 'object-storage',
|
||||||
|
storageProps: {
|
||||||
|
id: id
|
||||||
|
},
|
||||||
|
url: `${ config.drive.config.secure ? 'https' : 'http' }://${ config.drive.config.endPoint }${ config.drive.config.port ? ':' + config.drive.config.port : '' }/${ config.drive.bucket }/${ obj }`
|
||||||
|
});
|
||||||
|
|
||||||
|
const file = await DriveFile.insert({
|
||||||
|
length: size,
|
||||||
|
uploadDate: new Date(),
|
||||||
|
md5: hash,
|
||||||
|
filename: name,
|
||||||
|
metadata: metadata,
|
||||||
|
contentType: type
|
||||||
|
});
|
||||||
|
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Get MongoDB GridFS bucket
|
||||||
|
const bucket = await getDriveFileBucket();
|
||||||
|
|
||||||
|
return new Promise<IDriveFile>((resolve, reject) => {
|
||||||
const writeStream = bucket.openUploadStream(name, { contentType: type, metadata });
|
const writeStream = bucket.openUploadStream(name, { contentType: type, metadata });
|
||||||
writeStream.once('finish', resolve);
|
writeStream.once('finish', resolve);
|
||||||
writeStream.on('error', reject);
|
writeStream.on('error', reject);
|
||||||
readable.pipe(writeStream);
|
readable.pipe(writeStream);
|
||||||
}));
|
});
|
||||||
|
}
|
||||||
const writeThumbnailChunks = (name: string, readable: stream.Readable, originalId: mongodb.ObjectID) =>
|
}
|
||||||
getDriveFileThumbnailBucket()
|
|
||||||
.then(bucket => new Promise((resolve, reject) => {
|
|
||||||
const writeStream = bucket.openUploadStream(name, {
|
|
||||||
contentType: 'image/jpeg',
|
|
||||||
metadata: {
|
|
||||||
originalId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
writeStream.once('finish', resolve);
|
|
||||||
writeStream.on('error', reject);
|
|
||||||
readable.pipe(writeStream);
|
|
||||||
}));
|
|
||||||
|
|
||||||
async function deleteOldFile(user: IRemoteUser) {
|
async function deleteOldFile(user: IRemoteUser) {
|
||||||
const oldFile = await DriveFile.findOne({
|
const oldFile = await DriveFile.findOne({
|
||||||
|
@ -82,7 +101,7 @@ export default async function(
|
||||||
comment: string = null,
|
comment: string = null,
|
||||||
folderId: mongodb.ObjectID = null,
|
folderId: mongodb.ObjectID = null,
|
||||||
force: boolean = false,
|
force: boolean = false,
|
||||||
metaOnly: boolean = false,
|
isLink: boolean = false,
|
||||||
url: string = null,
|
url: string = null,
|
||||||
uri: string = null,
|
uri: string = null,
|
||||||
sensitive = false
|
sensitive = false
|
||||||
|
@ -150,7 +169,7 @@ export default async function(
|
||||||
}
|
}
|
||||||
|
|
||||||
//#region Check drive usage
|
//#region Check drive usage
|
||||||
if (!metaOnly) {
|
if (!isLink) {
|
||||||
const usage = await DriveFile
|
const usage = await DriveFile
|
||||||
.aggregate([{
|
.aggregate([{
|
||||||
$match: {
|
$match: {
|
||||||
|
@ -262,19 +281,23 @@ export default async function(
|
||||||
folderId: folder !== null ? folder._id : null,
|
folderId: folder !== null ? folder._id : null,
|
||||||
comment: comment,
|
comment: comment,
|
||||||
properties: properties,
|
properties: properties,
|
||||||
isMetaOnly: metaOnly,
|
withoutChunks: isLink,
|
||||||
isSensitive: sensitive
|
isSensitive: sensitive
|
||||||
} as IMetadata;
|
} as IMetadata;
|
||||||
|
|
||||||
if (url !== null) {
|
if (url !== null) {
|
||||||
metadata.url = url;
|
metadata.src = url;
|
||||||
|
|
||||||
|
if (isLink) {
|
||||||
|
metadata.url = url;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (uri !== null) {
|
if (uri !== null) {
|
||||||
metadata.uri = uri;
|
metadata.uri = uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
const driveFile = metaOnly
|
const driveFile = isLink
|
||||||
? await DriveFile.insert({
|
? await DriveFile.insert({
|
||||||
length: 0,
|
length: 0,
|
||||||
uploadDate: new Date(),
|
uploadDate: new Date(),
|
||||||
|
@ -283,7 +306,7 @@ export default async function(
|
||||||
metadata: metadata,
|
metadata: metadata,
|
||||||
contentType: mime
|
contentType: mime
|
||||||
})
|
})
|
||||||
: await (writeChunks(detectedName, fs.createReadStream(path), mime, metadata) as Promise<IDriveFile>);
|
: await (save(fs.createReadStream(path), detectedName, mime, hash, size, metadata));
|
||||||
|
|
||||||
log(`drive file has been created ${driveFile._id}`);
|
log(`drive file has been created ${driveFile._id}`);
|
||||||
|
|
||||||
|
@ -293,16 +316,7 @@ export default async function(
|
||||||
publishDriveStream(user._id, 'file_created', packedFile);
|
publishDriveStream(user._id, 'file_created', packedFile);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!metaOnly) {
|
// TODO: サムネイル生成
|
||||||
try {
|
|
||||||
const thumb = await genThumbnail(driveFile);
|
|
||||||
if (thumb) {
|
|
||||||
await writeThumbnailChunks(detectedName, thumb, driveFile._id);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// noop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return driveFile;
|
return driveFile;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,30 +1,40 @@
|
||||||
|
import * as Minio from 'minio';
|
||||||
import DriveFile, { DriveFileChunk, IDriveFile } from '../../models/drive-file';
|
import DriveFile, { DriveFileChunk, IDriveFile } from '../../models/drive-file';
|
||||||
import DriveFileThumbnail, { DriveFileThumbnailChunk } from '../../models/drive-file-thumbnail';
|
import DriveFileThumbnail, { DriveFileThumbnailChunk } from '../../models/drive-file-thumbnail';
|
||||||
|
import config from '../../config';
|
||||||
|
|
||||||
export default async function(file: IDriveFile, isExpired = false) {
|
export default async function(file: IDriveFile, isExpired = false) {
|
||||||
// チャンクをすべて削除
|
if (file.metadata.withoutChunks) {
|
||||||
await DriveFileChunk.remove({
|
if (file.metadata.storage == 'object-storage') {
|
||||||
files_id: file._id
|
const minio = new Minio.Client(config.drive.config);
|
||||||
});
|
const obj = `${config.drive.prefix}/${file.metadata.storageProps.id}`;
|
||||||
|
await minio.removeObject(config.drive.bucket, obj);
|
||||||
await DriveFile.update({ _id: file._id }, {
|
|
||||||
$set: {
|
|
||||||
'metadata.deletedAt': new Date(),
|
|
||||||
'metadata.isExpired': isExpired
|
|
||||||
}
|
}
|
||||||
});
|
} else {
|
||||||
|
// チャンクをすべて削除
|
||||||
//#region サムネイルもあれば削除
|
await DriveFileChunk.remove({
|
||||||
const thumbnail = await DriveFileThumbnail.findOne({
|
files_id: file._id
|
||||||
'metadata.originalId': file._id
|
|
||||||
});
|
|
||||||
|
|
||||||
if (thumbnail) {
|
|
||||||
await DriveFileThumbnailChunk.remove({
|
|
||||||
files_id: thumbnail._id
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await DriveFileThumbnail.remove({ _id: thumbnail._id });
|
await DriveFile.update({ _id: file._id }, {
|
||||||
|
$set: {
|
||||||
|
'metadata.deletedAt': new Date(),
|
||||||
|
'metadata.isExpired': isExpired
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//#region サムネイルもあれば削除
|
||||||
|
const thumbnail = await DriveFileThumbnail.findOne({
|
||||||
|
'metadata.originalId': file._id
|
||||||
|
});
|
||||||
|
|
||||||
|
if (thumbnail) {
|
||||||
|
await DriveFileThumbnailChunk.remove({
|
||||||
|
files_id: thumbnail._id
|
||||||
|
});
|
||||||
|
|
||||||
|
await DriveFileThumbnail.remove({ _id: thumbnail._id });
|
||||||
|
}
|
||||||
|
//#endregion
|
||||||
}
|
}
|
||||||
//#endregion
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue