Introduce account document to user document

An account document is attached to a user document if an account of the
user is on the server. It may be missing if the user is on a remote server.
This commit is contained in:
Akihiko Odaki 2018-03-26 00:19:07 +09:00
parent a633f184ab
commit 19b9cb105d
70 changed files with 355 additions and 280 deletions

View File

@ -34,7 +34,7 @@ export default (req: express.Request) => new Promise<IAuthContext>(async (resolv
if (isNativeToken(token)) { if (isNativeToken(token)) {
const user: IUser = await User const user: IUser = await User
.findOne({ token: token }); .findOne({ 'account.token': token });
if (user === null) { if (user === null) {
return reject('user not found'); return reject('user not found');

View File

@ -225,7 +225,7 @@ class SigninContext extends Context {
} }
} else { } else {
// Compare password // Compare password
const same = await bcrypt.compare(query, this.temporaryUser.password); const same = await bcrypt.compare(query, this.temporaryUser.account.password);
if (same) { if (same) {
this.bot.signin(this.temporaryUser); this.bot.signin(this.temporaryUser);

View File

@ -110,11 +110,11 @@ class LineBot extends BotCore {
data: `showtl|${user.id}` data: `showtl|${user.id}`
}); });
if (user.twitter) { if (user.account.twitter) {
actions.push({ actions.push({
type: 'uri', type: 'uri',
label: 'Twitterアカウントを見る', label: 'Twitterアカウントを見る',
uri: `https://twitter.com/${user.twitter.screen_name}` uri: `https://twitter.com/${user.account.twitter.screen_name}`
}); });
} }
@ -171,7 +171,7 @@ module.exports = async (app: express.Application) => {
if (session == null) { if (session == null) {
const user = await User.findOne({ const user = await User.findOne({
line: { 'account.line': {
user_id: sourceId user_id: sourceId
} }
}); });
@ -181,7 +181,7 @@ module.exports = async (app: express.Application) => {
bot.on('signin', user => { bot.on('signin', user => {
User.update(user._id, { User.update(user._id, {
$set: { $set: {
line: { 'account.line': {
user_id: sourceId user_id: sourceId
} }
} }
@ -191,7 +191,7 @@ module.exports = async (app: express.Application) => {
bot.on('signout', user => { bot.on('signout', user => {
User.update(user._id, { User.update(user._id, {
$set: { $set: {
line: { 'account.line': {
user_id: null user_id: null
} }
} }

View File

@ -2,7 +2,7 @@ import config from '../../conf';
export default function(res, user, redirect: boolean) { export default function(res, user, redirect: boolean) {
const expires = 1000 * 60 * 60 * 24 * 365; // One Year const expires = 1000 * 60 * 60 * 24 * 365; // One Year
res.cookie('i', user.token, { res.cookie('i', user.account.token, {
path: '/', path: '/',
domain: `.${config.host}`, domain: `.${config.host}`,
secure: config.url.substr(0, 5) === 'https', secure: config.url.substr(0, 5) === 'https',

View File

@ -45,7 +45,7 @@ module.exports = (params, user) => new Promise(async (res, rej) => {
// Fetch token // Fetch token
const session = await AuthSess const session = await AuthSess
.findOne({ token: token }); .findOne({ 'account.token': token });
if (session === null) { if (session === null) {
return rej('session not found'); return rej('session not found');

View File

@ -32,7 +32,7 @@ module.exports = (params, user) => new Promise(async (res, rej) => {
}, { }, {
fields: { fields: {
data: false, data: false,
profile: false 'account.profile': false
} }
}); });

View File

@ -31,7 +31,7 @@ module.exports = (params, user) => new Promise(async (res, rej) => {
}, { }, {
fields: { fields: {
data: false, data: false,
profile: false 'account.profile': false
} }
}); });

View File

@ -22,7 +22,7 @@ module.exports = (params, user, _, isSecure) => new Promise(async (res, rej) =>
// Update lastUsedAt // Update lastUsedAt
User.update({ _id: user._id }, { User.update({ _id: user._id }, {
$set: { $set: {
last_used_at: new Date() 'account.last_used_at': new Date()
} }
}); });
}); });

View File

@ -28,8 +28,8 @@ module.exports = async (params, user) => new Promise(async (res, rej) => {
await User.update(user._id, { await User.update(user._id, {
$set: { $set: {
two_factor_secret: user.two_factor_temp_secret, 'account.two_factor_secret': user.two_factor_temp_secret,
two_factor_enabled: true 'account.two_factor_enabled': true
} }
}); });

View File

@ -14,7 +14,7 @@ module.exports = async (params, user) => new Promise(async (res, rej) => {
if (passwordErr) return rej('invalid password param'); if (passwordErr) return rej('invalid password param');
// Compare password // Compare password
const same = await bcrypt.compare(password, user.password); const same = await bcrypt.compare(password, user.account.password);
if (!same) { if (!same) {
return rej('incorrect password'); return rej('incorrect password');

View File

@ -11,7 +11,7 @@ module.exports = async (params, user) => new Promise(async (res, rej) => {
if (passwordErr) return rej('invalid password param'); if (passwordErr) return rej('invalid password param');
// Compare password // Compare password
const same = await bcrypt.compare(password, user.password); const same = await bcrypt.compare(password, user.account.password);
if (!same) { if (!same) {
return rej('incorrect password'); return rej('incorrect password');
@ -19,8 +19,8 @@ module.exports = async (params, user) => new Promise(async (res, rej) => {
await User.update(user._id, { await User.update(user._id, {
$set: { $set: {
two_factor_secret: null, 'account.two_factor_secret': null,
two_factor_enabled: false 'account.two_factor_enabled': false
} }
}); });

View File

@ -22,7 +22,7 @@ module.exports = async (params, user) => new Promise(async (res, rej) => {
if (newPasswordErr) return rej('invalid new_password param'); if (newPasswordErr) return rej('invalid new_password param');
// Compare password // Compare password
const same = await bcrypt.compare(currentPassword, user.password); const same = await bcrypt.compare(currentPassword, user.account.password);
if (!same) { if (!same) {
return rej('incorrect password'); return rej('incorrect password');
@ -34,7 +34,7 @@ module.exports = async (params, user) => new Promise(async (res, rej) => {
await User.update(user._id, { await User.update(user._id, {
$set: { $set: {
password: hash 'account.password': hash
} }
}); });

View File

@ -20,7 +20,7 @@ module.exports = async (params, user) => new Promise(async (res, rej) => {
if (passwordErr) return rej('invalid password param'); if (passwordErr) return rej('invalid password param');
// Compare password // Compare password
const same = await bcrypt.compare(password, user.password); const same = await bcrypt.compare(password, user.account.password);
if (!same) { if (!same) {
return rej('incorrect password'); return rej('incorrect password');
@ -31,7 +31,7 @@ module.exports = async (params, user) => new Promise(async (res, rej) => {
await User.update(user._id, { await User.update(user._id, {
$set: { $set: {
token: secret 'account.token': secret
} }
}); });

View File

@ -29,12 +29,12 @@ module.exports = async (params, user, _, isSecure) => new Promise(async (res, re
// Get 'location' parameter // Get 'location' parameter
const [location, locationErr] = $(params.location).optional.nullable.string().pipe(isValidLocation).$; const [location, locationErr] = $(params.location).optional.nullable.string().pipe(isValidLocation).$;
if (locationErr) return rej('invalid location param'); if (locationErr) return rej('invalid location param');
if (location !== undefined) user.profile.location = location; if (location !== undefined) user.account.profile.location = location;
// Get 'birthday' parameter // Get 'birthday' parameter
const [birthday, birthdayErr] = $(params.birthday).optional.nullable.string().pipe(isValidBirthday).$; const [birthday, birthdayErr] = $(params.birthday).optional.nullable.string().pipe(isValidBirthday).$;
if (birthdayErr) return rej('invalid birthday param'); if (birthdayErr) return rej('invalid birthday param');
if (birthday !== undefined) user.profile.birthday = birthday; if (birthday !== undefined) user.account.profile.birthday = birthday;
// Get 'avatar_id' parameter // Get 'avatar_id' parameter
const [avatarId, avatarIdErr] = $(params.avatar_id).optional.id().$; const [avatarId, avatarIdErr] = $(params.avatar_id).optional.id().$;
@ -49,12 +49,12 @@ module.exports = async (params, user, _, isSecure) => new Promise(async (res, re
// Get 'is_bot' parameter // Get 'is_bot' parameter
const [isBot, isBotErr] = $(params.is_bot).optional.boolean().$; const [isBot, isBotErr] = $(params.is_bot).optional.boolean().$;
if (isBotErr) return rej('invalid is_bot param'); if (isBotErr) return rej('invalid is_bot param');
if (isBot != null) user.is_bot = isBot; if (isBot != null) user.account.is_bot = isBot;
// Get 'auto_watch' parameter // Get 'auto_watch' parameter
const [autoWatch, autoWatchErr] = $(params.auto_watch).optional.boolean().$; const [autoWatch, autoWatchErr] = $(params.auto_watch).optional.boolean().$;
if (autoWatchErr) return rej('invalid auto_watch param'); if (autoWatchErr) return rej('invalid auto_watch param');
if (autoWatch != null) user.settings.auto_watch = autoWatch; if (autoWatch != null) user.account.settings.auto_watch = autoWatch;
await User.update(user._id, { await User.update(user._id, {
$set: { $set: {
@ -62,9 +62,9 @@ module.exports = async (params, user, _, isSecure) => new Promise(async (res, re
description: user.description, description: user.description,
avatar_id: user.avatar_id, avatar_id: user.avatar_id,
banner_id: user.banner_id, banner_id: user.banner_id,
profile: user.profile, 'account.profile': user.account.profile,
is_bot: user.is_bot, 'account.is_bot': user.account.is_bot,
settings: user.settings 'account.settings': user.account.settings
} }
}); });

View File

@ -22,14 +22,14 @@ module.exports = async (params, user) => new Promise(async (res, rej) => {
if (valueErr) return rej('invalid value param'); if (valueErr) return rej('invalid value param');
const x = {}; const x = {};
x[`client_settings.${name}`] = value; x[`account.client_settings.${name}`] = value;
await User.update(user._id, { await User.update(user._id, {
$set: x $set: x
}); });
// Serialize // Serialize
user.client_settings[name] = value; user.account.client_settings[name] = value;
const iObj = await pack(user, user, { const iObj = await pack(user, user, {
detail: true, detail: true,
includeSecrets: true includeSecrets: true

View File

@ -26,7 +26,7 @@ module.exports = async (params, user) => new Promise(async (res, rej) => {
if (home) { if (home) {
await User.update(user._id, { await User.update(user._id, {
$set: { $set: {
'client_settings.home': home 'account.client_settings.home': home
} }
}); });
@ -38,7 +38,7 @@ module.exports = async (params, user) => new Promise(async (res, rej) => {
} else { } else {
if (id == null && data == null) return rej('you need to set id and data params if home param unset'); if (id == null && data == null) return rej('you need to set id and data params if home param unset');
const _home = user.client_settings.home; const _home = user.account.client_settings.home;
const widget = _home.find(w => w.id == id); const widget = _home.find(w => w.id == id);
if (widget == null) return rej('widget not found'); if (widget == null) return rej('widget not found');
@ -47,7 +47,7 @@ module.exports = async (params, user) => new Promise(async (res, rej) => {
await User.update(user._id, { await User.update(user._id, {
$set: { $set: {
'client_settings.home': _home 'account.client_settings.home': _home
} }
}); });

View File

@ -25,7 +25,7 @@ module.exports = async (params, user) => new Promise(async (res, rej) => {
if (home) { if (home) {
await User.update(user._id, { await User.update(user._id, {
$set: { $set: {
'client_settings.mobile_home': home 'account.client_settings.mobile_home': home
} }
}); });
@ -37,7 +37,7 @@ module.exports = async (params, user) => new Promise(async (res, rej) => {
} else { } else {
if (id == null && data == null) return rej('you need to set id and data params if home param unset'); if (id == null && data == null) return rej('you need to set id and data params if home param unset');
const _home = user.client_settings.mobile_home || []; const _home = user.account.client_settings.mobile_home || [];
const widget = _home.find(w => w.id == id); const widget = _home.find(w => w.id == id);
if (widget == null) return rej('widget not found'); if (widget == null) return rej('widget not found');
@ -46,7 +46,7 @@ module.exports = async (params, user) => new Promise(async (res, rej) => {
await User.update(user._id, { await User.update(user._id, {
$set: { $set: {
'client_settings.mobile_home': _home 'account.client_settings.mobile_home': _home
} }
}); });

View File

@ -30,7 +30,7 @@ module.exports = (params, user) => new Promise(async (res, rej) => {
}, { }, {
fields: { fields: {
data: false, data: false,
profile: false 'account.profile': false
} }
}); });

View File

@ -30,7 +30,7 @@ module.exports = (params, user) => new Promise(async (res, rej) => {
}, { }, {
fields: { fields: {
data: false, data: false,
profile: false 'account.profile': false
} }
}); });

View File

@ -12,7 +12,7 @@ import Post from '../../models/post';
* @return {Promise<any>} * @return {Promise<any>}
*/ */
module.exports = (params, user) => new Promise(async (res, rej) => { module.exports = (params, user) => new Promise(async (res, rej) => {
if (!user.is_pro) { if (!user.account.is_pro) {
return rej('This endpoint is available only from a Pro account'); return rej('This endpoint is available only from a Pro account');
} }

View File

@ -390,7 +390,7 @@ module.exports = (params, user: IUser, app) => new Promise(async (res, rej) => {
}); });
// この投稿をWatchする // この投稿をWatchする
if (user.settings.auto_watch !== false) { if (user.account.settings.auto_watch !== false) {
watch(user._id, reply); watch(user._id, reply);
} }

View File

@ -100,7 +100,7 @@ module.exports = (params, user) => new Promise(async (res, rej) => {
}); });
// この投稿をWatchする // この投稿をWatchする
if (user.settings.auto_watch !== false) { if (user.account.settings.auto_watch !== false) {
watch(user._id, post); watch(user._id, post);
} }
}); });

View File

@ -116,7 +116,7 @@ module.exports = (params, user) => new Promise(async (res, rej) => {
}); });
// この投稿をWatchする // この投稿をWatchする
if (user.settings.auto_watch !== false) { if (user.account.settings.auto_watch !== false) {
watch(user._id, post); watch(user._id, post);
} }
}); });

View File

@ -30,7 +30,7 @@ module.exports = (params, me) => new Promise(async (res, rej) => {
_id: { _id: {
$nin: followingIds $nin: followingIds
}, },
last_used_at: { 'account.last_used_at': {
$gte: new Date(Date.now() - ms('7days')) $gte: new Date(Date.now() - ms('7days'))
} }
}, { }, {

View File

@ -11,7 +11,7 @@ import config from '../../conf';
const User = db.get<IUser>('users'); const User = db.get<IUser>('users');
User.createIndex('username'); User.createIndex('username');
User.createIndex('token'); User.createIndex('account.token');
export default User; export default User;
@ -43,46 +43,48 @@ export type IUser = {
_id: mongo.ObjectID; _id: mongo.ObjectID;
created_at: Date; created_at: Date;
deleted_at: Date; deleted_at: Date;
email: string;
followers_count: number; followers_count: number;
following_count: number; following_count: number;
links: string[];
name: string; name: string;
password: string;
posts_count: number; posts_count: number;
drive_capacity: number; drive_capacity: number;
username: string; username: string;
username_lower: string; username_lower: string;
token: string;
avatar_id: mongo.ObjectID; avatar_id: mongo.ObjectID;
banner_id: mongo.ObjectID; banner_id: mongo.ObjectID;
data: any; data: any;
twitter: {
access_token: string;
access_token_secret: string;
user_id: string;
screen_name: string;
};
line: {
user_id: string;
};
description: string; description: string;
profile: {
location: string;
birthday: string; // 'YYYY-MM-DD'
tags: string[];
};
last_used_at: Date;
latest_post: IPost; latest_post: IPost;
pinned_post_id: mongo.ObjectID; pinned_post_id: mongo.ObjectID;
is_bot: boolean;
is_pro: boolean;
is_suspended: boolean; is_suspended: boolean;
keywords: string[]; keywords: string[];
two_factor_secret: string; account: {
two_factor_enabled: boolean; email: string;
client_settings: any; links: string[];
settings: any; password: string;
token: string;
twitter: {
access_token: string;
access_token_secret: string;
user_id: string;
screen_name: string;
};
line: {
user_id: string;
};
profile: {
location: string;
birthday: string; // 'YYYY-MM-DD'
tags: string[];
};
last_used_at: Date;
is_bot: boolean;
is_pro: boolean;
two_factor_secret: string;
two_factor_enabled: boolean;
client_settings: any;
settings: any;
};
}; };
export function init(user): IUser { export function init(user): IUser {
@ -119,11 +121,11 @@ export const pack = (
const fields = opts.detail ? { const fields = opts.detail ? {
} : { } : {
settings: false, 'account.settings': false,
client_settings: false, 'account.client_settings': false,
profile: false, 'account.profile': false,
keywords: false, 'account.keywords': false,
domains: false 'account.domains': false
}; };
// Populate the user if 'user' is ID // Populate the user if 'user' is ID
@ -158,26 +160,26 @@ export const pack = (
delete _user.latest_post; delete _user.latest_post;
// Remove private properties // Remove private properties
delete _user.password; delete _user.account.password;
delete _user.token; delete _user.account.token;
delete _user.two_factor_temp_secret; delete _user.account.two_factor_temp_secret;
delete _user.two_factor_secret; delete _user.account.two_factor_secret;
delete _user.username_lower; delete _user.username_lower;
if (_user.twitter) { if (_user.account.twitter) {
delete _user.twitter.access_token; delete _user.account.twitter.access_token;
delete _user.twitter.access_token_secret; delete _user.account.twitter.access_token_secret;
} }
delete _user.line; delete _user.account.line;
// Visible via only the official client // Visible via only the official client
if (!opts.includeSecrets) { if (!opts.includeSecrets) {
delete _user.email; delete _user.account.email;
delete _user.settings; delete _user.account.settings;
delete _user.client_settings; delete _user.account.client_settings;
} }
if (!opts.detail) { if (!opts.detail) {
delete _user.two_factor_enabled; delete _user.account.two_factor_enabled;
} }
_user.avatar_url = _user.avatar_id != null _user.avatar_url = _user.avatar_id != null

View File

@ -36,7 +36,7 @@ export default async (req: express.Request, res: express.Response) => {
}, { }, {
fields: { fields: {
data: false, data: false,
profile: false 'account.profile': false
} }
}); });
@ -48,12 +48,12 @@ export default async (req: express.Request, res: express.Response) => {
} }
// Compare password // Compare password
const same = await bcrypt.compare(password, user.password); const same = await bcrypt.compare(password, user.account.password);
if (same) { if (same) {
if (user.two_factor_enabled) { if (user.account.two_factor_enabled) {
const verified = (speakeasy as any).totp.verify({ const verified = (speakeasy as any).totp.verify({
secret: user.two_factor_secret, secret: user.account.two_factor_secret,
encoding: 'base32', encoding: 'base32',
token: token token: token
}); });

View File

@ -105,38 +105,40 @@ export default async (req: express.Request, res: express.Response) => {
// Create account // Create account
const account: IUser = await User.insert({ const account: IUser = await User.insert({
token: secret,
avatar_id: null, avatar_id: null,
banner_id: null, banner_id: null,
created_at: new Date(), created_at: new Date(),
description: null, description: null,
email: null,
followers_count: 0, followers_count: 0,
following_count: 0, following_count: 0,
links: null,
name: name, name: name,
password: hash,
posts_count: 0, posts_count: 0,
likes_count: 0, likes_count: 0,
liked_count: 0, liked_count: 0,
drive_capacity: 1073741824, // 1GB drive_capacity: 1073741824, // 1GB
username: username, username: username,
username_lower: username.toLowerCase(), username_lower: username.toLowerCase(),
profile: { account: {
bio: null, token: secret,
birthday: null, email: null,
blood: null, links: null,
gender: null, password: hash,
handedness: null, profile: {
height: null, bio: null,
location: null, birthday: null,
weight: null blood: null,
}, gender: null,
settings: { handedness: null,
auto_watch: true height: null,
}, location: null,
client_settings: { weight: null
home: homeData },
settings: {
auto_watch: true
},
client_settings: {
home: homeData
}
} }
}); });

View File

@ -39,10 +39,10 @@ module.exports = (app: express.Application) => {
if (userToken == null) return res.send('plz signin'); if (userToken == null) return res.send('plz signin');
const user = await User.findOneAndUpdate({ const user = await User.findOneAndUpdate({
token: userToken 'account.token': userToken
}, { }, {
$set: { $set: {
twitter: null 'account.twitter': null
} }
}); });
@ -126,7 +126,7 @@ module.exports = (app: express.Application) => {
const result = await twAuth.done(JSON.parse(ctx), req.query.oauth_verifier); const result = await twAuth.done(JSON.parse(ctx), req.query.oauth_verifier);
const user = await User.findOne({ const user = await User.findOne({
'twitter.user_id': result.userId 'account.twitter.user_id': result.userId
}); });
if (user == null) { if (user == null) {
@ -148,10 +148,10 @@ module.exports = (app: express.Application) => {
const result = await twAuth.done(JSON.parse(ctx), verifier); const result = await twAuth.done(JSON.parse(ctx), verifier);
const user = await User.findOneAndUpdate({ const user = await User.findOneAndUpdate({
token: userToken 'account.token': userToken
}, { }, {
$set: { $set: {
twitter: { 'account.twitter': {
access_token: result.accessToken, access_token: result.accessToken,
access_token_secret: result.accessTokenSecret, access_token_secret: result.accessTokenSecret,
user_id: result.userId, user_id: result.userId,

View File

@ -74,7 +74,7 @@ export default async function(request: websocket.request, connection: websocket.
// Update lastUsedAt // Update lastUsedAt
User.update({ _id: user._id }, { User.update({ _id: user._id }, {
$set: { $set: {
last_used_at: new Date() 'account.last_used_at': new Date()
} }
}); });
break; break;

View File

@ -94,7 +94,7 @@ function authenticate(token: string): Promise<IUser> {
// Fetch user // Fetch user
const user: IUser = await User const user: IUser = await User
.findOne({ .findOne({
token: token 'account.token': token
}); });
resolve(user); resolve(user);

View File

@ -7,6 +7,6 @@ import { IUser } from '../api/models/user';
export default function(user: IUser): string { export default function(user: IUser): string {
return `${user.name} (@${user.username})\n` + return `${user.name} (@${user.username})\n` +
`${user.posts_count}投稿、${user.following_count}フォロー、${user.followers_count}フォロワー\n` + `${user.posts_count}投稿、${user.following_count}フォロー、${user.followers_count}フォロワー\n` +
`場所: ${user.profile.location}、誕生日: ${user.profile.birthday}\n` + `場所: ${user.account.profile.location}、誕生日: ${user.account.profile.birthday}\n` +
`${user.description}`; `${user.description}`;
} }

View File

@ -56,14 +56,14 @@ export default function<T extends object>(data: {
id: this.id, id: this.id,
data: newProps data: newProps
}).then(() => { }).then(() => {
(this as any).os.i.client_settings.mobile_home.find(w => w.id == this.id).data = newProps; (this as any).os.i.account.client_settings.mobile_home.find(w => w.id == this.id).data = newProps;
}); });
} else { } else {
(this as any).api('i/update_home', { (this as any).api('i/update_home', {
id: this.id, id: this.id,
data: newProps data: newProps
}).then(() => { }).then(() => {
(this as any).os.i.client_settings.home.find(w => w.id == this.id).data = newProps; (this as any).os.i.account.client_settings.home.find(w => w.id == this.id).data = newProps;
}); });
} }
}, { }, {

View File

@ -270,7 +270,7 @@ export default class MiOS extends EventEmitter {
// Parse response // Parse response
res.json().then(i => { res.json().then(i => {
me = i; me = i;
me.token = token; me.account.token = token;
done(); done();
}); });
}) })
@ -294,12 +294,12 @@ export default class MiOS extends EventEmitter {
const fetched = me => { const fetched = me => {
if (me) { if (me) {
// デフォルトの設定をマージ // デフォルトの設定をマージ
me.client_settings = Object.assign({ me.account.client_settings = Object.assign({
fetchOnScroll: true, fetchOnScroll: true,
showMaps: true, showMaps: true,
showPostFormOnTopOfTl: false, showPostFormOnTopOfTl: false,
gradientWindowHeader: false gradientWindowHeader: false
}, me.client_settings); }, me.account.client_settings);
// ローカルストレージにキャッシュ // ローカルストレージにキャッシュ
localStorage.setItem('me', JSON.stringify(me)); localStorage.setItem('me', JSON.stringify(me));
@ -329,7 +329,7 @@ export default class MiOS extends EventEmitter {
fetched(cachedMe); fetched(cachedMe);
// 後から新鮮なデータをフェッチ // 後から新鮮なデータをフェッチ
fetchme(cachedMe.token, freshData => { fetchme(cachedMe.account.token, freshData => {
merge(cachedMe, freshData); merge(cachedMe, freshData);
}); });
} else { } else {
@ -437,7 +437,7 @@ export default class MiOS extends EventEmitter {
} }
// Append a credential // Append a credential
if (this.isSignedIn) (data as any).i = this.i.token; if (this.isSignedIn) (data as any).i = this.i.account.token;
// TODO // TODO
//const viaStream = localStorage.getItem('enableExperimental') == 'true'; //const viaStream = localStorage.getItem('enableExperimental') == 'true';

View File

@ -8,7 +8,7 @@ import MiOS from '../../mios';
export class DriveStream extends Stream { export class DriveStream extends Stream {
constructor(os: MiOS, me) { constructor(os: MiOS, me) {
super(os, 'drive', { super(os, 'drive', {
i: me.token i: me.account.token
}); });
} }
} }

View File

@ -10,13 +10,13 @@ import MiOS from '../../mios';
export class HomeStream extends Stream { export class HomeStream extends Stream {
constructor(os: MiOS, me) { constructor(os: MiOS, me) {
super(os, '', { super(os, '', {
i: me.token i: me.account.token
}); });
// 最終利用日時を更新するため定期的にaliveメッセージを送信 // 最終利用日時を更新するため定期的にaliveメッセージを送信
setInterval(() => { setInterval(() => {
this.send({ type: 'alive' }); this.send({ type: 'alive' });
me.last_used_at = new Date(); me.account.last_used_at = new Date();
}, 1000 * 60); }, 1000 * 60);
// 自分の情報が更新されたとき // 自分の情報が更新されたとき

View File

@ -8,7 +8,7 @@ import MiOS from '../../mios';
export class MessagingIndexStream extends Stream { export class MessagingIndexStream extends Stream {
constructor(os: MiOS, me) { constructor(os: MiOS, me) {
super(os, 'messaging-index', { super(os, 'messaging-index', {
i: me.token i: me.account.token
}); });
} }
} }

View File

@ -7,13 +7,13 @@ import MiOS from '../../mios';
export class MessagingStream extends Stream { export class MessagingStream extends Stream {
constructor(os: MiOS, me, otherparty) { constructor(os: MiOS, me, otherparty) {
super(os, 'messaging', { super(os, 'messaging', {
i: me.token, i: me.account.token,
otherparty otherparty
}); });
(this as any).on('_connected_', () => { (this as any).on('_connected_', () => {
this.send({ this.send({
i: me.token i: me.account.token
}); });
}); });
} }

View File

@ -4,7 +4,7 @@ import MiOS from '../../mios';
export class OthelloGameStream extends Stream { export class OthelloGameStream extends Stream {
constructor(os: MiOS, me, game) { constructor(os: MiOS, me, game) {
super(os, 'othello-game', { super(os, 'othello-game', {
i: me ? me.token : null, i: me ? me.account.token : null,
game: game.id game: game.id
}); });
} }

View File

@ -5,7 +5,7 @@ import MiOS from '../../mios';
export class OthelloStream extends Stream { export class OthelloStream extends Stream {
constructor(os: MiOS, me) { constructor(os: MiOS, me) {
super(os, 'othello', { super(os, 'othello', {
i: me.token i: me.account.token
}); });
} }
} }

View File

@ -6,7 +6,7 @@
<label class="password"> <label class="password">
<input v-model="password" type="password" placeholder="%i18n:common.tags.mk-signin.password%" required/>%fa:lock% <input v-model="password" type="password" placeholder="%i18n:common.tags.mk-signin.password%" required/>%fa:lock%
</label> </label>
<label class="token" v-if="user && user.two_factor_enabled"> <label class="token" v-if="user && user.account.two_factor_enabled">
<input v-model="token" type="number" placeholder="%i18n:common.tags.mk-signin.token%" required/>%fa:lock% <input v-model="token" type="number" placeholder="%i18n:common.tags.mk-signin.token%" required/>%fa:lock%
</label> </label>
<button type="submit" :disabled="signing">{{ signing ? '%i18n:common.tags.mk-signin.signing-in%' : '%i18n:common.tags.mk-signin.signin%' }}</button> <button type="submit" :disabled="signing">{{ signing ? '%i18n:common.tags.mk-signin.signing-in%' : '%i18n:common.tags.mk-signin.signin%' }}</button>
@ -40,7 +40,7 @@ export default Vue.extend({
(this as any).api('signin', { (this as any).api('signin', {
username: this.username, username: this.username,
password: this.password, password: this.password,
token: this.user && this.user.two_factor_enabled ? this.token : undefined token: this.user && this.user.account.two_factor_enabled ? this.token : undefined
}).then(() => { }).then(() => {
location.reload(); location.reload();
}).catch(() => { }).catch(() => {

View File

@ -1,13 +1,13 @@
<template> <template>
<div class="mk-twitter-setting"> <div class="mk-twitter-setting">
<p>%i18n:common.tags.mk-twitter-setting.description%<a :href="`${docsUrl}/link-to-twitter`" target="_blank">%i18n:common.tags.mk-twitter-setting.detail%</a></p> <p>%i18n:common.tags.mk-twitter-setting.description%<a :href="`${docsUrl}/link-to-twitter`" target="_blank">%i18n:common.tags.mk-twitter-setting.detail%</a></p>
<p class="account" v-if="os.i.twitter" :title="`Twitter ID: ${os.i.twitter.user_id}`">%i18n:common.tags.mk-twitter-setting.connected-to%: <a :href="`https://twitter.com/${os.i.twitter.screen_name}`" target="_blank">@{{ os.i.twitter.screen_name }}</a></p> <p class="account" v-if="os.i.account.twitter" :title="`Twitter ID: ${os.i.account.twitter.user_id}`">%i18n:common.tags.mk-twitter-setting.connected-to%: <a :href="`https://twitter.com/${os.i.account.twitter.screen_name}`" target="_blank">@{{ os.i.account.twitter.screen_name }}</a></p>
<p> <p>
<a :href="`${apiUrl}/connect/twitter`" target="_blank" @click.prevent="connect">{{ os.i.twitter ? '%i18n:common.tags.mk-twitter-setting.reconnect%' : '%i18n:common.tags.mk-twitter-setting.connect%' }}</a> <a :href="`${apiUrl}/connect/twitter`" target="_blank" @click.prevent="connect">{{ os.i.account.twitter ? '%i18n:common.tags.mk-twitter-setting.reconnect%' : '%i18n:common.tags.mk-twitter-setting.connect%' }}</a>
<span v-if="os.i.twitter"> or </span> <span v-if="os.i.account.twitter"> or </span>
<a :href="`${apiUrl}/disconnect/twitter`" target="_blank" v-if="os.i.twitter" @click.prevent="disconnect">%i18n:common.tags.mk-twitter-setting.disconnect%</a> <a :href="`${apiUrl}/disconnect/twitter`" target="_blank" v-if="os.i.account.twitter" @click.prevent="disconnect">%i18n:common.tags.mk-twitter-setting.disconnect%</a>
</p> </p>
<p class="id" v-if="os.i.twitter">Twitter ID: {{ os.i.twitter.user_id }}</p> <p class="id" v-if="os.i.account.twitter">Twitter ID: {{ os.i.account.twitter.user_id }}</p>
</div> </div>
</template> </template>
@ -25,7 +25,7 @@ export default Vue.extend({
}, },
mounted() { mounted() {
this.$watch('os.i', () => { this.$watch('os.i', () => {
if ((this as any).os.i.twitter) { if ((this as any).os.i.account.twitter) {
if (this.form) this.form.close(); if (this.form) this.form.close();
} }
}, { }, {

View File

@ -50,7 +50,7 @@ export default Vue.extend({
reader.readAsDataURL(file); reader.readAsDataURL(file);
const data = new FormData(); const data = new FormData();
data.append('i', (this as any).os.i.token); data.append('i', (this as any).os.i.account.token);
data.append('file', file); data.append('file', file);
if (folder) data.append('folder_id', folder); if (folder) data.append('folder_id', folder);

View File

@ -16,7 +16,7 @@ export default (os: OS) => (cb, file = null) => {
w.$once('cropped', blob => { w.$once('cropped', blob => {
const data = new FormData(); const data = new FormData();
data.append('i', os.i.token); data.append('i', os.i.account.token);
data.append('file', blob, file.name + '.cropped.png'); data.append('file', blob, file.name + '.cropped.png');
os.api('drive/folders/find', { os.api('drive/folders/find', {

View File

@ -16,7 +16,7 @@ export default (os: OS) => (cb, file = null) => {
w.$once('cropped', blob => { w.$once('cropped', blob => {
const data = new FormData(); const data = new FormData();
data.append('i', os.i.token); data.append('i', os.i.account.token);
data.append('file', blob, file.name + '.cropped.png'); data.append('file', blob, file.name + '.cropped.png');
os.api('drive/folders/find', { os.api('drive/folders/find', {

View File

@ -53,7 +53,7 @@
<div class="main"> <div class="main">
<a @click="hint">カスタマイズのヒント</a> <a @click="hint">カスタマイズのヒント</a>
<div> <div>
<mk-post-form v-if="os.i.client_settings.showPostFormOnTopOfTl"/> <mk-post-form v-if="os.i.account.client_settings.showPostFormOnTopOfTl"/>
<mk-timeline ref="tl" @loaded="onTlLoaded"/> <mk-timeline ref="tl" @loaded="onTlLoaded"/>
</div> </div>
</div> </div>
@ -63,7 +63,7 @@
<component v-for="widget in widgets[place]" :is="`mkw-${widget.name}`" :key="widget.id" :ref="widget.id" :widget="widget" @chosen="warp"/> <component v-for="widget in widgets[place]" :is="`mkw-${widget.name}`" :key="widget.id" :ref="widget.id" :widget="widget" @chosen="warp"/>
</div> </div>
<div class="main"> <div class="main">
<mk-post-form v-if="os.i.client_settings.showPostFormOnTopOfTl"/> <mk-post-form v-if="os.i.account.client_settings.showPostFormOnTopOfTl"/>
<mk-timeline ref="tl" @loaded="onTlLoaded" v-if="mode == 'timeline'"/> <mk-timeline ref="tl" @loaded="onTlLoaded" v-if="mode == 'timeline'"/>
<mk-mentions @loaded="onTlLoaded" v-if="mode == 'mentions'"/> <mk-mentions @loaded="onTlLoaded" v-if="mode == 'mentions'"/>
</div> </div>
@ -104,16 +104,16 @@ export default Vue.extend({
home: { home: {
get(): any[] { get(): any[] {
//#region //#region
(this as any).os.i.client_settings.home.forEach(w => { (this as any).os.i.account.client_settings.home.forEach(w => {
if (w.name == 'rss-reader') w.name = 'rss'; if (w.name == 'rss-reader') w.name = 'rss';
if (w.name == 'user-recommendation') w.name = 'users'; if (w.name == 'user-recommendation') w.name = 'users';
if (w.name == 'recommended-polls') w.name = 'polls'; if (w.name == 'recommended-polls') w.name = 'polls';
}); });
//#endregion //#endregion
return (this as any).os.i.client_settings.home; return (this as any).os.i.account.client_settings.home;
}, },
set(value) { set(value) {
(this as any).os.i.client_settings.home = value; (this as any).os.i.account.client_settings.home = value;
} }
}, },
left(): any[] { left(): any[] {
@ -126,7 +126,7 @@ export default Vue.extend({
created() { created() {
this.widgets.left = this.left; this.widgets.left = this.left;
this.widgets.right = this.right; this.widgets.right = this.right;
this.$watch('os.i.client_settings', i => { this.$watch('os.i.account.client_settings', i => {
this.widgets.left = this.left; this.widgets.left = this.left;
this.widgets.right = this.right; this.widgets.right = this.right;
}, { }, {
@ -161,17 +161,17 @@ export default Vue.extend({
}, },
onHomeUpdated(data) { onHomeUpdated(data) {
if (data.home) { if (data.home) {
(this as any).os.i.client_settings.home = data.home; (this as any).os.i.account.client_settings.home = data.home;
this.widgets.left = data.home.filter(w => w.place == 'left'); this.widgets.left = data.home.filter(w => w.place == 'left');
this.widgets.right = data.home.filter(w => w.place == 'right'); this.widgets.right = data.home.filter(w => w.place == 'right');
} else { } else {
const w = (this as any).os.i.client_settings.home.find(w => w.id == data.id); const w = (this as any).os.i.account.client_settings.home.find(w => w.id == data.id);
if (w != null) { if (w != null) {
w.data = data.data; w.data = data.data;
this.$refs[w.id][0].preventSave = true; this.$refs[w.id][0].preventSave = true;
this.$refs[w.id][0].props = w.data; this.$refs[w.id][0].props = w.data;
this.widgets.left = (this as any).os.i.client_settings.home.filter(w => w.place == 'left'); this.widgets.left = (this as any).os.i.account.client_settings.home.filter(w => w.place == 'left');
this.widgets.right = (this as any).os.i.client_settings.home.filter(w => w.place == 'right'); this.widgets.right = (this as any).os.i.account.client_settings.home.filter(w => w.place == 'right');
} }
} }
}, },

View File

@ -148,7 +148,7 @@ export default Vue.extend({
// Draw map // Draw map
if (this.p.geo) { if (this.p.geo) {
const shouldShowMap = (this as any).os.isSignedIn ? (this as any).os.i.client_settings.showMaps : true; const shouldShowMap = (this as any).os.isSignedIn ? (this as any).os.i.account.client_settings.showMaps : true;
if (shouldShowMap) { if (shouldShowMap) {
(this as any).os.getGoogleMaps().then(maps => { (this as any).os.getGoogleMaps().then(maps => {
const uluru = new maps.LatLng(this.p.geo.latitude, this.p.geo.longitude); const uluru = new maps.LatLng(this.p.geo.latitude, this.p.geo.longitude);

View File

@ -22,7 +22,7 @@
<div class="main"> <div class="main">
<header> <header>
<router-link class="name" :to="`/${p.user.username}`" v-user-preview="p.user.id">{{ p.user.name }}</router-link> <router-link class="name" :to="`/${p.user.username}`" v-user-preview="p.user.id">{{ p.user.name }}</router-link>
<span class="is-bot" v-if="p.user.is_bot">bot</span> <span class="is-bot" v-if="p.user.account.is_bot">bot</span>
<span class="username">@{{ p.user.username }}</span> <span class="username">@{{ p.user.username }}</span>
<div class="info"> <div class="info">
<span class="app" v-if="p.app">via <b>{{ p.app.name }}</b></span> <span class="app" v-if="p.app">via <b>{{ p.app.name }}</b></span>
@ -162,7 +162,7 @@ export default Vue.extend({
// Draw map // Draw map
if (this.p.geo) { if (this.p.geo) {
const shouldShowMap = (this as any).os.isSignedIn ? (this as any).os.i.client_settings.showMaps : true; const shouldShowMap = (this as any).os.isSignedIn ? (this as any).os.i.account.client_settings.showMaps : true;
if (shouldShowMap) { if (shouldShowMap) {
(this as any).os.getGoogleMaps().then(maps => { (this as any).os.getGoogleMaps().then(maps => {
const uluru = new maps.LatLng(this.p.geo.latitude, this.p.geo.longitude); const uluru = new maps.LatLng(this.p.geo.latitude, this.p.geo.longitude);

View File

@ -2,8 +2,8 @@
<div class="2fa"> <div class="2fa">
<p>%i18n:desktop.tags.mk-2fa-setting.intro%<a href="%i18n:desktop.tags.mk-2fa-setting.url%" target="_blank">%i18n:desktop.tags.mk-2fa-setting.detail%</a></p> <p>%i18n:desktop.tags.mk-2fa-setting.intro%<a href="%i18n:desktop.tags.mk-2fa-setting.url%" target="_blank">%i18n:desktop.tags.mk-2fa-setting.detail%</a></p>
<div class="ui info warn"><p>%fa:exclamation-triangle%%i18n:desktop.tags.mk-2fa-setting.caution%</p></div> <div class="ui info warn"><p>%fa:exclamation-triangle%%i18n:desktop.tags.mk-2fa-setting.caution%</p></div>
<p v-if="!data && !os.i.two_factor_enabled"><button @click="register" class="ui primary">%i18n:desktop.tags.mk-2fa-setting.register%</button></p> <p v-if="!data && !os.i.account.two_factor_enabled"><button @click="register" class="ui primary">%i18n:desktop.tags.mk-2fa-setting.register%</button></p>
<template v-if="os.i.two_factor_enabled"> <template v-if="os.i.account.two_factor_enabled">
<p>%i18n:desktop.tags.mk-2fa-setting.already-registered%</p> <p>%i18n:desktop.tags.mk-2fa-setting.already-registered%</p>
<button @click="unregister" class="ui">%i18n:desktop.tags.mk-2fa-setting.unregister%</button> <button @click="unregister" class="ui">%i18n:desktop.tags.mk-2fa-setting.unregister%</button>
</template> </template>
@ -54,7 +54,7 @@ export default Vue.extend({
password: password password: password
}).then(() => { }).then(() => {
(this as any).apis.notify('%i18n:desktop.tags.mk-2fa-setting.unregistered%'); (this as any).apis.notify('%i18n:desktop.tags.mk-2fa-setting.unregistered%');
(this as any).os.i.two_factor_enabled = false; (this as any).os.i.account.two_factor_enabled = false;
}); });
}); });
}, },
@ -64,7 +64,7 @@ export default Vue.extend({
token: this.token token: this.token
}).then(() => { }).then(() => {
(this as any).apis.notify('%i18n:desktop.tags.mk-2fa-setting.success%'); (this as any).apis.notify('%i18n:desktop.tags.mk-2fa-setting.success%');
(this as any).os.i.two_factor_enabled = true; (this as any).os.i.account.two_factor_enabled = true;
}).catch(() => { }).catch(() => {
(this as any).apis.notify('%i18n:desktop.tags.mk-2fa-setting.failed%'); (this as any).apis.notify('%i18n:desktop.tags.mk-2fa-setting.failed%');
}); });

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="root api"> <div class="root api">
<p>Token: <code>{{ os.i.token }}</code></p> <p>Token: <code>{{ os.i.account.token }}</code></p>
<p>%i18n:desktop.tags.mk-api-info.intro%</p> <p>%i18n:desktop.tags.mk-api-info.intro%</p>
<div class="ui info warn"><p>%fa:exclamation-triangle%%i18n:desktop.tags.mk-api-info.caution%</p></div> <div class="ui info warn"><p>%fa:exclamation-triangle%%i18n:desktop.tags.mk-api-info.caution%</p></div>
<p>%i18n:desktop.tags.mk-api-info.regeneration-of-token%</p> <p>%i18n:desktop.tags.mk-api-info.regeneration-of-token%</p>

View File

@ -24,7 +24,7 @@
<button class="ui primary" @click="save">%i18n:desktop.tags.mk-profile-setting.save%</button> <button class="ui primary" @click="save">%i18n:desktop.tags.mk-profile-setting.save%</button>
<section> <section>
<h2>その他</h2> <h2>その他</h2>
<mk-switch v-model="os.i.is_bot" @change="onChangeIsBot" text="このアカウントはbotです"/> <mk-switch v-model="os.i.account.is_bot" @change="onChangeIsBot" text="このアカウントはbotです"/>
</section> </section>
</div> </div>
</template> </template>
@ -43,9 +43,9 @@ export default Vue.extend({
}, },
created() { created() {
this.name = (this as any).os.i.name; this.name = (this as any).os.i.name;
this.location = (this as any).os.i.profile.location; this.location = (this as any).os.i.account.profile.location;
this.description = (this as any).os.i.description; this.description = (this as any).os.i.description;
this.birthday = (this as any).os.i.profile.birthday; this.birthday = (this as any).os.i.account.profile.birthday;
}, },
methods: { methods: {
updateAvatar() { updateAvatar() {
@ -63,7 +63,7 @@ export default Vue.extend({
}, },
onChangeIsBot() { onChangeIsBot() {
(this as any).api('i/update', { (this as any).api('i/update', {
is_bot: (this as any).os.i.is_bot is_bot: (this as any).os.i.account.is_bot
}); });
} }
} }

View File

@ -20,7 +20,7 @@
<section class="web" v-show="page == 'web'"> <section class="web" v-show="page == 'web'">
<h1>動作</h1> <h1>動作</h1>
<mk-switch v-model="os.i.client_settings.fetchOnScroll" @change="onChangeFetchOnScroll" text="スクロールで自動読み込み"> <mk-switch v-model="os.i.account.client_settings.fetchOnScroll" @change="onChangeFetchOnScroll" text="スクロールで自動読み込み">
<span>ページを下までスクロールしたときに自動で追加のコンテンツを読み込みます</span> <span>ページを下までスクロールしたときに自動で追加のコンテンツを読み込みます</span>
</mk-switch> </mk-switch>
<mk-switch v-model="autoPopout" text="ウィンドウの自動ポップアウト"> <mk-switch v-model="autoPopout" text="ウィンドウの自動ポップアウト">
@ -33,11 +33,11 @@
<div class="div"> <div class="div">
<button class="ui button" @click="customizeHome">ホームをカスタマイズ</button> <button class="ui button" @click="customizeHome">ホームをカスタマイズ</button>
</div> </div>
<mk-switch v-model="os.i.client_settings.showPostFormOnTopOfTl" @change="onChangeShowPostFormOnTopOfTl" text="タイムライン上部に投稿フォームを表示する"/> <mk-switch v-model="os.i.account.client_settings.showPostFormOnTopOfTl" @change="onChangeShowPostFormOnTopOfTl" text="タイムライン上部に投稿フォームを表示する"/>
<mk-switch v-model="os.i.client_settings.showMaps" @change="onChangeShowMaps" text="マップの自動展開"> <mk-switch v-model="os.i.account.client_settings.showMaps" @change="onChangeShowMaps" text="マップの自動展開">
<span>位置情報が添付された投稿のマップを自動的に展開します</span> <span>位置情報が添付された投稿のマップを自動的に展開します</span>
</mk-switch> </mk-switch>
<mk-switch v-model="os.i.client_settings.gradientWindowHeader" @change="onChangeGradientWindowHeader" text="ウィンドウのタイトルバーにグラデーションを使用"/> <mk-switch v-model="os.i.account.client_settings.gradientWindowHeader" @change="onChangeGradientWindowHeader" text="ウィンドウのタイトルバーにグラデーションを使用"/>
</section> </section>
<section class="web" v-show="page == 'web'"> <section class="web" v-show="page == 'web'">
@ -57,7 +57,7 @@
<section class="web" v-show="page == 'web'"> <section class="web" v-show="page == 'web'">
<h1>モバイル</h1> <h1>モバイル</h1>
<mk-switch v-model="os.i.client_settings.disableViaMobile" @change="onChangeDisableViaMobile" text="「モバイルからの投稿」フラグを付けない"/> <mk-switch v-model="os.i.account.client_settings.disableViaMobile" @change="onChangeDisableViaMobile" text="「モバイルからの投稿」フラグを付けない"/>
</section> </section>
<section class="web" v-show="page == 'web'"> <section class="web" v-show="page == 'web'">
@ -86,7 +86,7 @@
<section class="notification" v-show="page == 'notification'"> <section class="notification" v-show="page == 'notification'">
<h1>通知</h1> <h1>通知</h1>
<mk-switch v-model="os.i.settings.auto_watch" @change="onChangeAutoWatch" text="投稿の自動ウォッチ"> <mk-switch v-model="os.i.account.settings.auto_watch" @change="onChangeAutoWatch" text="投稿の自動ウォッチ">
<span>リアクションしたり返信したりした投稿に関する通知を自動的に受け取るようにします</span> <span>リアクションしたり返信したりした投稿に関する通知を自動的に受け取るようにします</span>
</mk-switch> </mk-switch>
</section> </section>

View File

@ -107,7 +107,7 @@ export default Vue.extend({
this.fetch(); this.fetch();
}, },
onScroll() { onScroll() {
if ((this as any).os.i.client_settings.fetchOnScroll !== false) { if ((this as any).os.i.account.client_settings.fetchOnScroll !== false) {
const current = window.scrollY + window.innerHeight; const current = window.scrollY + window.innerHeight;
if (current > document.body.offsetHeight - 8) this.more(); if (current > document.body.offsetHeight - 8) this.more();
} }

View File

@ -44,9 +44,9 @@ export default Vue.extend({
}, },
mounted() { mounted() {
if ((this as any).os.isSignedIn) { if ((this as any).os.isSignedIn) {
const ago = (new Date().getTime() - new Date((this as any).os.i.last_used_at).getTime()) / 1000 const ago = (new Date().getTime() - new Date((this as any).os.i.account.last_used_at).getTime()) / 1000
const isHisasiburi = ago >= 3600; const isHisasiburi = ago >= 3600;
(this as any).os.i.last_used_at = new Date(); (this as any).os.i.account.last_used_at = new Date();
if (isHisasiburi) { if (isHisasiburi) {
(this.$refs.welcomeback as any).style.display = 'block'; (this.$refs.welcomeback as any).style.display = 'block';
(this.$refs.main as any).style.overflow = 'hidden'; (this.$refs.main as any).style.overflow = 'hidden';

View File

@ -24,8 +24,8 @@ export default Vue.extend({
computed: { computed: {
withGradient(): boolean { withGradient(): boolean {
return (this as any).os.isSignedIn return (this as any).os.isSignedIn
? (this as any).os.i.client_settings.gradientWindowHeader != null ? (this as any).os.i.account.client_settings.gradientWindowHeader != null
? (this as any).os.i.client_settings.gradientWindowHeader ? (this as any).os.i.account.client_settings.gradientWindowHeader
: false : false
: false; : false;
} }

View File

@ -92,8 +92,8 @@ export default Vue.extend({
}, },
withGradient(): boolean { withGradient(): boolean {
return (this as any).os.isSignedIn return (this as any).os.isSignedIn
? (this as any).os.i.client_settings.gradientWindowHeader != null ? (this as any).os.i.account.client_settings.gradientWindowHeader != null
? (this as any).os.i.client_settings.gradientWindowHeader ? (this as any).os.i.account.client_settings.gradientWindowHeader
: false : false
: false; : false;
} }

View File

@ -9,7 +9,7 @@
<div class="title"> <div class="title">
<p class="name">{{ user.name }}</p> <p class="name">{{ user.name }}</p>
<p class="username">@{{ user.username }}</p> <p class="username">@{{ user.username }}</p>
<p class="location" v-if="user.profile.location">%fa:map-marker%{{ user.profile.location }}</p> <p class="location" v-if="user.account.profile.location">%fa:map-marker%{{ user.account.profile.location }}</p>
</div> </div>
<footer> <footer>
<router-link :to="`/${user.username}`" :data-active="$parent.page == 'home'">%fa:home%概要</router-link> <router-link :to="`/${user.username}`" :data-active="$parent.page == 'home'">%fa:home%概要</router-link>

View File

@ -5,7 +5,7 @@
<x-profile :user="user"/> <x-profile :user="user"/>
<x-photos :user="user"/> <x-photos :user="user"/>
<x-followers-you-know v-if="os.isSignedIn && os.i.id != user.id" :user="user"/> <x-followers-you-know v-if="os.isSignedIn && os.i.id != user.id" :user="user"/>
<p>%i18n:desktop.tags.mk-user.last-used-at%: <b><mk-time :time="user.last_used_at"/></b></p> <p>%i18n:desktop.tags.mk-user.last-used-at%: <b><mk-time :time="user.account.last_used_at"/></b></p>
</div> </div>
</div> </div>
<main> <main>

View File

@ -7,11 +7,11 @@
<p v-if="!user.is_muted"><a @click="mute">%i18n:desktop.tags.mk-user.mute%</a></p> <p v-if="!user.is_muted"><a @click="mute">%i18n:desktop.tags.mk-user.mute%</a></p>
</div> </div>
<div class="description" v-if="user.description">{{ user.description }}</div> <div class="description" v-if="user.description">{{ user.description }}</div>
<div class="birthday" v-if="user.profile.birthday"> <div class="birthday" v-if="user.account.profile.birthday">
<p>%fa:birthday-cake%{{ user.profile.birthday.replace('-', '年').replace('-', '月') + '日' }} ({{ age }})</p> <p>%fa:birthday-cake%{{ user.account.profile.birthday.replace('-', '年').replace('-', '月') + '日' }} ({{ age }})</p>
</div> </div>
<div class="twitter" v-if="user.twitter"> <div class="twitter" v-if="user.account.twitter">
<p>%fa:B twitter%<a :href="`https://twitter.com/${user.twitter.screen_name}`" target="_blank">@{{ user.twitter.screen_name }}</a></p> <p>%fa:B twitter%<a :href="`https://twitter.com/${user.account.twitter.screen_name}`" target="_blank">@{{ user.account.twitter.screen_name }}</a></p>
</div> </div>
<div class="status"> <div class="status">
<p class="posts-count">%fa:angle-right%<a>{{ user.posts_count }}</a><b>投稿</b></p> <p class="posts-count">%fa:angle-right%<a>{{ user.posts_count }}</a><b>投稿</b></p>
@ -31,7 +31,7 @@ export default Vue.extend({
props: ['user'], props: ['user'],
computed: { computed: {
age(): number { age(): number {
return age(this.user.profile.birthday); return age(this.user.account.profile.birthday);
} }
}, },
methods: { methods: {

View File

@ -144,7 +144,7 @@ export default Vue.extend({
// Draw map // Draw map
if (this.p.geo) { if (this.p.geo) {
const shouldShowMap = (this as any).os.isSignedIn ? (this as any).os.i.client_settings.showMaps : true; const shouldShowMap = (this as any).os.isSignedIn ? (this as any).os.i.account.client_settings.showMaps : true;
if (shouldShowMap) { if (shouldShowMap) {
(this as any).os.getGoogleMaps().then(maps => { (this as any).os.getGoogleMaps().then(maps => {
const uluru = new maps.LatLng(this.p.geo.latitude, this.p.geo.longitude); const uluru = new maps.LatLng(this.p.geo.latitude, this.p.geo.longitude);

View File

@ -111,7 +111,7 @@ export default Vue.extend({
}, },
post() { post() {
this.posting = true; this.posting = true;
const viaMobile = (this as any).os.i.client_settings.disableViaMobile !== true; const viaMobile = (this as any).os.i.account.client_settings.disableViaMobile !== true;
(this as any).api('posts/create', { (this as any).api('posts/create', {
text: this.text == '' ? undefined : this.text, text: this.text == '' ? undefined : this.text,
media_ids: this.files.length > 0 ? this.files.map(f => f.id) : undefined, media_ids: this.files.length > 0 ? this.files.map(f => f.id) : undefined,

View File

@ -22,7 +22,7 @@
<div class="main"> <div class="main">
<header> <header>
<router-link class="name" :to="`/${p.user.username}`">{{ p.user.name }}</router-link> <router-link class="name" :to="`/${p.user.username}`">{{ p.user.name }}</router-link>
<span class="is-bot" v-if="p.user.is_bot">bot</span> <span class="is-bot" v-if="p.user.account.is_bot">bot</span>
<span class="username">@{{ p.user.username }}</span> <span class="username">@{{ p.user.username }}</span>
<div class="info"> <div class="info">
<span class="mobile" v-if="p.via_mobile">%fa:mobile-alt%</span> <span class="mobile" v-if="p.via_mobile">%fa:mobile-alt%</span>
@ -137,7 +137,7 @@ export default Vue.extend({
// Draw map // Draw map
if (this.p.geo) { if (this.p.geo) {
const shouldShowMap = (this as any).os.isSignedIn ? (this as any).os.i.client_settings.showMaps : true; const shouldShowMap = (this as any).os.isSignedIn ? (this as any).os.i.account.client_settings.showMaps : true;
if (shouldShowMap) { if (shouldShowMap) {
(this as any).os.getGoogleMaps().then(maps => { (this as any).os.getGoogleMaps().then(maps => {
const uluru = new maps.LatLng(this.p.geo.latitude, this.p.geo.longitude); const uluru = new maps.LatLng(this.p.geo.latitude, this.p.geo.longitude);

View File

@ -57,9 +57,9 @@ export default Vue.extend({
} }
}); });
const ago = (new Date().getTime() - new Date((this as any).os.i.last_used_at).getTime()) / 1000 const ago = (new Date().getTime() - new Date((this as any).os.i.account.last_used_at).getTime()) / 1000
const isHisasiburi = ago >= 3600; const isHisasiburi = ago >= 3600;
(this as any).os.i.last_used_at = new Date(); (this as any).os.i.account.last_used_at = new Date();
if (isHisasiburi) { if (isHisasiburi) {
(this.$refs.welcomeback as any).style.display = 'block'; (this.$refs.welcomeback as any).style.display = 'block';
(this.$refs.main as any).style.overflow = 'hidden'; (this.$refs.main as any).style.overflow = 'hidden';

View File

@ -82,8 +82,8 @@ export default Vue.extend({
}; };
}, },
created() { created() {
if ((this as any).os.i.client_settings.mobile_home == null) { if ((this as any).os.i.account.client_settings.mobile_home == null) {
Vue.set((this as any).os.i.client_settings, 'mobile_home', [{ Vue.set((this as any).os.i.account.client_settings, 'mobile_home', [{
name: 'calendar', name: 'calendar',
id: 'a', data: {} id: 'a', data: {}
}, { }, {
@ -105,14 +105,14 @@ export default Vue.extend({
name: 'version', name: 'version',
id: 'g', data: {} id: 'g', data: {}
}]); }]);
this.widgets = (this as any).os.i.client_settings.mobile_home; this.widgets = (this as any).os.i.account.client_settings.mobile_home;
this.saveHome(); this.saveHome();
} else { } else {
this.widgets = (this as any).os.i.client_settings.mobile_home; this.widgets = (this as any).os.i.account.client_settings.mobile_home;
} }
this.$watch('os.i.client_settings', i => { this.$watch('os.i.account.client_settings', i => {
this.widgets = (this as any).os.i.client_settings.mobile_home; this.widgets = (this as any).os.i.account.client_settings.mobile_home;
}, { }, {
deep: true deep: true
}); });
@ -157,15 +157,15 @@ export default Vue.extend({
}, },
onHomeUpdated(data) { onHomeUpdated(data) {
if (data.home) { if (data.home) {
(this as any).os.i.client_settings.mobile_home = data.home; (this as any).os.i.account.client_settings.mobile_home = data.home;
this.widgets = data.home; this.widgets = data.home;
} else { } else {
const w = (this as any).os.i.client_settings.mobile_home.find(w => w.id == data.id); const w = (this as any).os.i.account.client_settings.mobile_home.find(w => w.id == data.id);
if (w != null) { if (w != null) {
w.data = data.data; w.data = data.data;
this.$refs[w.id][0].preventSave = true; this.$refs[w.id][0].preventSave = true;
this.$refs[w.id][0].props = w.data; this.$refs[w.id][0].props = w.data;
this.widgets = (this as any).os.i.client_settings.mobile_home; this.widgets = (this as any).os.i.account.client_settings.mobile_home;
} }
} }
}, },
@ -194,7 +194,7 @@ export default Vue.extend({
this.saveHome(); this.saveHome();
}, },
saveHome() { saveHome() {
(this as any).os.i.client_settings.mobile_home = this.widgets; (this as any).os.i.account.client_settings.mobile_home = this.widgets;
(this as any).api('i/update_mobile_home', { (this as any).api('i/update_mobile_home', {
home: this.widgets home: this.widgets
}); });

View File

@ -53,9 +53,9 @@ export default Vue.extend({
}, },
created() { created() {
this.name = (this as any).os.i.name; this.name = (this as any).os.i.name;
this.location = (this as any).os.i.profile.location; this.location = (this as any).os.i.account.profile.location;
this.description = (this as any).os.i.description; this.description = (this as any).os.i.description;
this.birthday = (this as any).os.i.profile.birthday; this.birthday = (this as any).os.i.account.profile.birthday;
}, },
mounted() { mounted() {
document.title = 'Misskey | %i18n:mobile.tags.mk-profile-setting-page.title%'; document.title = 'Misskey | %i18n:mobile.tags.mk-profile-setting-page.title%';

View File

@ -18,11 +18,11 @@
</div> </div>
<div class="description">{{ user.description }}</div> <div class="description">{{ user.description }}</div>
<div class="info"> <div class="info">
<p class="location" v-if="user.profile.location"> <p class="location" v-if="user.account.profile.location">
%fa:map-marker%{{ user.profile.location }} %fa:map-marker%{{ user.account.profile.location }}
</p> </p>
<p class="birthday" v-if="user.profile.birthday"> <p class="birthday" v-if="user.account.profile.birthday">
%fa:birthday-cake%{{ user.profile.birthday.replace('-', '年').replace('-', '月') + '日' }} ({{ age }}) %fa:birthday-cake%{{ user.account.profile.birthday.replace('-', '年').replace('-', '月') + '日' }} ({{ age }})
</p> </p>
</div> </div>
<div class="status"> <div class="status">
@ -74,7 +74,7 @@ export default Vue.extend({
}, },
computed: { computed: {
age(): number { age(): number {
return age(this.user.profile.birthday); return age(this.user.account.profile.birthday);
} }
}, },
watch: { watch: {

View File

@ -31,7 +31,7 @@
<x-followers-you-know :user="user"/> <x-followers-you-know :user="user"/>
</div> </div>
</section> </section>
<p>%i18n:mobile.tags.mk-user-overview.last-used-at%: <b><mk-time :time="user.last_used_at"/></b></p> <p>%i18n:mobile.tags.mk-user-overview.last-used-at%: <b><mk-time :time="user.account.last_used_at"/></b></p>
</div> </div>
</template> </template>

View File

@ -8,7 +8,7 @@
<form @submit.prevent="onSubmit"> <form @submit.prevent="onSubmit">
<input v-model="username" type="text" pattern="^[a-zA-Z0-9-]+$" placeholder="ユーザー名" autofocus required @change="onUsernameChange"/> <input v-model="username" type="text" pattern="^[a-zA-Z0-9-]+$" placeholder="ユーザー名" autofocus required @change="onUsernameChange"/>
<input v-model="password" type="password" placeholder="パスワード" required/> <input v-model="password" type="password" placeholder="パスワード" required/>
<input v-if="user && user.two_factor_enabled" v-model="token" type="number" placeholder="トークン" required/> <input v-if="user && user.account.two_factor_enabled" v-model="token" type="number" placeholder="トークン" required/>
<button type="submit" :disabled="signing">{{ signing ? 'ログインしています' : 'ログイン' }}</button> <button type="submit" :disabled="signing">{{ signing ? 'ログインしています' : 'ログイン' }}</button>
</form> </form>
<div> <div>
@ -70,7 +70,7 @@ export default Vue.extend({
(this as any).api('signin', { (this as any).api('signin', {
username: this.username, username: this.username,
password: this.password, password: this.password,
token: this.user && this.user.two_factor_enabled ? this.token : undefined token: this.user && this.user.account.two_factor_enabled ? this.token : undefined
}).then(() => { }).then(() => {
location.reload(); location.reload();
}).catch(() => { }).catch(() => {

View File

@ -81,12 +81,6 @@ props:
desc: desc:
ja: "自分がこのユーザーをミュートしているか" ja: "自分がこのユーザーをミュートしているか"
en: "Whether you muted this user" en: "Whether you muted this user"
- name: "last_used_at"
type: "date"
optional: false
desc:
ja: "最終利用日時"
en: "The last used date of this user"
- name: "posts_count" - name: "posts_count"
type: "number" type: "number"
optional: false optional: false
@ -111,49 +105,63 @@ props:
desc: desc:
ja: "ドライブの容量(bytes)" ja: "ドライブの容量(bytes)"
en: "The capacity of drive of this user (bytes)" en: "The capacity of drive of this user (bytes)"
- name: "is_bot" - name: "account"
type: "boolean"
optional: true
desc:
ja: "botか否か(自己申告であることに留意)"
en: "Whether is bot or not"
- name: "twitter"
type: "object"
optional: true
desc:
ja: "連携されているTwitterアカウント情報"
en: "The info of the connected twitter account of this user"
defName: "twitter"
def:
- name: "user_id"
type: "string"
optional: false
desc:
ja: "ユーザーID"
en: "The user ID"
- name: "screen_name"
type: "string"
optional: false
desc:
ja: "ユーザー名"
en: "The screen name of this user"
- name: "profile"
type: "object" type: "object"
optional: false optional: false
desc: desc:
ja: "プロフィール" ja: "このサーバーにおけるアカウント"
en: "The profile of this user" en: "The account of this user on this server"
defName: "profile" defName: "account"
def: def:
- name: "location" - name: "last_used_at"
type: "string" type: "date"
optional: false
desc:
ja: "最終利用日時"
en: "The last used date of this user"
- name: "is_bot"
type: "boolean"
optional: true optional: true
desc: desc:
ja: "場所" ja: "botか否か(自己申告であることに留意)"
en: "The location of this user" en: "Whether is bot or not"
- name: "birthday" - name: "twitter"
type: "string" type: "object"
optional: true optional: true
desc: desc:
ja: "誕生日 (YYYY-MM-DD)" ja: "連携されているTwitterアカウント情報"
en: "The birthday of this user (YYYY-MM-DD)" en: "The info of the connected twitter account of this user"
defName: "twitter"
def:
- name: "user_id"
type: "string"
optional: false
desc:
ja: "ユーザーID"
en: "The user ID"
- name: "screen_name"
type: "string"
optional: false
desc:
ja: "ユーザー名"
en: "The screen name of this user"
- name: "profile"
type: "object"
optional: false
desc:
ja: "プロフィール"
en: "The profile of this user"
defName: "profile"
def:
- name: "location"
type: "string"
optional: true
desc:
ja: "場所"
en: "The location of this user"
- name: "birthday"
type: "string"
optional: true
desc:
ja: "誕生日 (YYYY-MM-DD)"
en: "The birthday of this user (YYYY-MM-DD)"

View File

@ -30,7 +30,7 @@ const async = fn => (done) => {
const request = (endpoint, params, me) => new Promise((ok, ng) => { const request = (endpoint, params, me) => new Promise((ok, ng) => {
const auth = me ? { const auth = me ? {
i: me.token i: me.account.token
} : {}; } : {};
_chai.request(server) _chai.request(server)
@ -136,8 +136,10 @@ describe('API', () => {
describe('i/update', () => { describe('i/update', () => {
it('アカウント設定を更新できる', async(async () => { it('アカウント設定を更新できる', async(async () => {
const me = await insertSakurako({ const me = await insertSakurako({
profile: { account: {
gender: 'female' profile: {
gender: 'female'
}
} }
}); });
@ -153,10 +155,10 @@ describe('API', () => {
res.should.have.status(200); res.should.have.status(200);
res.body.should.be.a('object'); res.body.should.be.a('object');
res.body.should.have.property('name').eql(myName); res.body.should.have.property('name').eql(myName);
res.body.should.have.property('profile').a('object'); res.body.should.have.nested.property('account.profile').a('object');
res.body.should.have.nested.property('profile.location').eql(myLocation); res.body.should.have.nested.property('account.profile.location').eql(myLocation);
res.body.should.have.nested.property('profile.birthday').eql(myBirthday); res.body.should.have.nested.property('account.profile.birthday').eql(myBirthday);
res.body.should.have.nested.property('profile.gender').eql('female'); res.body.should.have.nested.property('account.profile.gender').eql('female');
})); }));
it('名前を空白にできない', async(async () => { it('名前を空白にできない', async(async () => {
@ -176,8 +178,8 @@ describe('API', () => {
}, me); }, me);
res.should.have.status(200); res.should.have.status(200);
res.body.should.be.a('object'); res.body.should.be.a('object');
res.body.should.have.property('profile').a('object'); res.body.should.have.nested.property('account.profile').a('object');
res.body.should.have.nested.property('profile.birthday').eql(null); res.body.should.have.nested.property('account.profile.birthday').eql(null);
})); }));
it('不正な誕生日の形式で怒られる', async(async () => { it('不正な誕生日の形式で怒られる', async(async () => {
@ -764,7 +766,7 @@ describe('API', () => {
const me = await insertSakurako(); const me = await insertSakurako();
const res = await _chai.request(server) const res = await _chai.request(server)
.post('/drive/files/create') .post('/drive/files/create')
.field('i', me.token) .field('i', me.account.token)
.attach('file', fs.readFileSync(__dirname + '/resources/Lenna.png'), 'Lenna.png'); .attach('file', fs.readFileSync(__dirname + '/resources/Lenna.png'), 'Lenna.png');
res.should.have.status(200); res.should.have.status(200);
res.body.should.be.a('object'); res.body.should.be.a('object');
@ -1138,27 +1140,47 @@ describe('API', () => {
}); });
}); });
function deepAssign(destination, ...sources) {
for (const source of sources) {
for (const key in source) {
const destinationChild = destination[key];
if (typeof destinationChild === 'object' && destinationChild != null) {
deepAssign(destinationChild, source[key]);
} else {
destination[key] = source[key];
}
}
}
return destination;
}
function insertSakurako(opts) { function insertSakurako(opts) {
return db.get('users').insert(Object.assign({ return db.get('users').insert(deepAssign({
token: '!00000000000000000000000000000000',
username: 'sakurako', username: 'sakurako',
username_lower: 'sakurako', username_lower: 'sakurako',
password: '$2a$08$FnHXg3tP.M/kINWgQSXNqeoBsiVrkj.ecXX8mW9rfBzMRkibYfjYy', // HimawariDaisuki06160907 account: {
profile: {}, token: '!00000000000000000000000000000000',
settings: {}, password: '$2a$08$FnHXg3tP.M/kINWgQSXNqeoBsiVrkj.ecXX8mW9rfBzMRkibYfjYy', // HimawariDaisuki06160907
client_settings: {} profile: {},
settings: {},
client_settings: {}
}
}, opts)); }, opts));
} }
function insertHimawari(opts) { function insertHimawari(opts) {
return db.get('users').insert(Object.assign({ return db.get('users').insert(deepAssign({
token: '!00000000000000000000000000000001',
username: 'himawari', username: 'himawari',
username_lower: 'himawari', username_lower: 'himawari',
password: '$2a$08$OPESxR2RE/ZijjGanNKk6ezSqGFitqsbZqTjWUZPLhORMKxHCbc4O', // ilovesakurako account: {
profile: {}, token: '!00000000000000000000000000000001',
settings: {}, password: '$2a$08$OPESxR2RE/ZijjGanNKk6ezSqGFitqsbZqTjWUZPLhORMKxHCbc4O', // ilovesakurako
client_settings: {} profile: {},
settings: {},
client_settings: {}
}
}, opts)); }, opts));
} }
@ -1171,14 +1193,14 @@ function insertDriveFile(opts) {
} }
function insertDriveFolder(opts) { function insertDriveFolder(opts) {
return db.get('drive_folders').insert(Object.assign({ return db.get('drive_folders').insert(deepAssign({
name: 'my folder', name: 'my folder',
parent_id: null parent_id: null
}, opts)); }, opts));
} }
function insertApp(opts) { function insertApp(opts) {
return db.get('apps').insert(Object.assign({ return db.get('apps').insert(deepAssign({
name: 'my app', name: 'my app',
secret: 'mysecret' secret: 'mysecret'
}, opts)); }, opts));

View File

@ -0,0 +1,41 @@
db.users.dropIndex({ token: 1 });
db.users.find({}).forEach(function(user) {
print(user._id);
db.users.update({ _id: user._id }, {
$unset: {
email: '',
links: '',
password: '',
token: '',
twitter: '',
line: '',
profile: '',
last_used_at: '',
is_bot: '',
is_pro: '',
two_factor_secret: '',
two_factor_enabled: '',
client_settings: '',
settings: ''
},
$set: {
account: {
email: user.email,
links: user.links,
password: user.password,
token: user.token,
twitter: user.twitter,
line: user.line,
profile: user.profile,
last_used_at: user.last_used_at,
is_bot: user.is_bot,
is_pro: user.is_pro,
two_factor_secret: user.two_factor_secret,
two_factor_enabled: user.two_factor_enabled,
client_settings: user.client_settings,
settings: user.settings
}
}
}, false, false);
});