1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-01-11 18:24:43 +02:00

Server: Allow manually deleting a user flag

This commit is contained in:
Laurent Cozic 2021-09-28 17:42:00 +01:00
parent 5da820aa0a
commit 3a11885705
6 changed files with 57 additions and 26 deletions

View File

@ -1,3 +1,7 @@
#user_cancel_subscription_link {
display: none;
}
.user-flags ul > li {
list-style-type: none;
}

View File

@ -34,7 +34,7 @@ async function handleUserFlags(ctx: AppContext): Promise<NotificationView> {
const user = ctx.joplin.owner;
const flags = await ctx.joplin.models.userFlag().allByUserId(ctx.joplin.owner.id);
const flagStrings = flags.map(f => `- ${userFlagToString(f)}`);
const flagStrings = flags.map(f => `- ${userFlagToString(f)}`).join('\n');
if (!user.enabled || !user.can_upload) {
return {

View File

@ -79,6 +79,8 @@ export default class UserFlagModels extends BaseModel<UserFlag> {
}
public async removeMulti(userId: Uuid, flagTypes: UserFlagType[]) {
if (!flagTypes.length) return;
await this.withTransaction(async () => {
for (const flagType of flagTypes) {
await this.remove(userId, flagType, { updateUser: false });

View File

@ -4,7 +4,7 @@ import { RouteType } from '../../utils/types';
import { AppContext, HttpMethod } from '../../utils/types';
import { bodyFields, formParse } from '../../utils/requestUtils';
import { ErrorForbidden, ErrorUnprocessableEntity } from '../../utils/errors';
import { User, UserFlagType, Uuid } from '../../services/database/types';
import { User, UserFlag, UserFlagType, Uuid } from '../../services/database/types';
import config from '../../config';
import { View } from '../../services/MustacheService';
import defaultView from '../../utils/defaultView';
@ -146,11 +146,18 @@ router.get('users/:id', async (path: SubPath, ctx: AppContext, user: User = null
postUrl = `${config().baseUrl}/users/${user.id}`;
}
let userFlags: string[] = isNew ? [] : (await models.userFlag().allByUserId(user.id)).map(f => {
return userFlagToString(f);
interface UserFlagView extends UserFlag {
message: string;
}
let userFlagViews: UserFlagView[] = isNew ? [] : (await models.userFlag().allByUserId(user.id)).map(f => {
return {
...f,
message: userFlagToString(f),
};
});
if (!owner.is_admin) userFlags = [];
if (!owner.is_admin) userFlagViews = [];
const subscription = !isNew ? await ctx.joplin.models.subscription().byUserId(userId) : null;
@ -179,8 +186,8 @@ router.get('users/:id', async (path: SubPath, ctx: AppContext, user: User = null
view.content.showResetPasswordButton = !isNew && owner.is_admin && user.enabled;
view.content.canShareFolderOptions = yesNoDefaultOptions(user, 'can_share_folder');
view.content.canUploadOptions = yesNoOptions(user, 'can_upload');
view.content.hasFlags = !!userFlags.length;
view.content.userFlags = userFlags;
view.content.hasFlags = !!userFlagViews.length;
view.content.userFlagViews = userFlagViews;
view.content.stripePortalUrl = stripePortalUrl();
view.jsFiles.push('zxcvbn');
@ -296,6 +303,7 @@ interface FormFields {
// user_cancel_subscription_button: string;
impersonate_button: string;
stop_impersonate_button: string;
delete_user_flags: string;
}
router.post('users', async (path: SubPath, ctx: AppContext) => {
@ -326,13 +334,14 @@ router.post('users', async (path: SubPath, ctx: AppContext) => {
await models.user().save(userToSave, { isNew: false });
}
// } else if (fields.user_cancel_subscription_button) {
// await cancelSubscriptionByUserId(models, userId);
// const sessionId = contextSessionId(ctx, false);
// if (sessionId) {
// await models.session().logout(sessionId);
// return redirect(ctx, config().baseUrl);
// }
// } else if (fields.user_cancel_subscription_button) {
// await cancelSubscriptionByUserId(models, userId);
// const sessionId = contextSessionId(ctx, false);
// if (sessionId) {
// await models.session().logout(sessionId);
// return redirect(ctx, config().baseUrl);
// }
} else if (fields.stop_impersonate_button) {
await stopImpersonating(ctx);
return redirect(ctx, config().baseUrl);
@ -354,6 +363,16 @@ router.post('users', async (path: SubPath, ctx: AppContext) => {
await updateSubscriptionType(models, userId, AccountType.Basic);
} else if (fields.update_subscription_pro_button) {
await updateSubscriptionType(models, userId, AccountType.Pro);
} else if (fields.delete_user_flags) {
const userFlagTypes: UserFlagType[] = [];
for (const key of Object.keys(fields)) {
if (key.startsWith('user_flag_')) {
const type = Number(key.substr(10));
userFlagTypes.push(type);
}
}
await models.userFlag().removeMulti(userId, userFlagTypes);
} else {
throw new Error('Invalid form button');
}

View File

@ -20,6 +20,7 @@ export async function csrfCheck(ctx: AppContext, isPublicRoute: boolean) {
const fields = await bodyFields<BodyWithCsrfToken>(ctx.req);
if (!fields._csrf) throw new ErrorForbidden('CSRF token is missing');
if (Array.isArray(fields._csrf)) throw new Error('Multiple CSRF tokens inside the form!');
if (!(await ctx.joplin.models.token().isValid(userId, fields._csrf))) {
throw new ErrorForbidden(`Invalid CSRF token: ${fields._csrf}`);

View File

@ -1,6 +1,6 @@
<h1 class="title">Your profile</h1>
<form id="user_form" action="{{{postUrl}}}" method="POST">
<form id="user_form" action="{{{postUrl}}}" method="POST" class="block">
<div class="block">
{{> errorBanner}}
@ -140,19 +140,24 @@
</div>
{{/subscription}}
{{#hasFlags}}
<div class="content">
<h1 class="title">Flags</h1>
{{#userFlags}}
<ul>
<li>{{.}}</li>
</ul>
{{/userFlags}}
</div>
{{/hasFlags}}
</form>
{{#hasFlags}}
<div class="content user-flags block">
<h1 class="title">Flags</h1>
<form action="{{{postUrl}}}" method="POST">
{{{csrfTag}}}
{{#userFlagViews}}
<ul>
<li><label class="checkbox"><input type="checkbox" name="user_flag_{{type}}"> {{message}}</label></li>
</ul>
{{/userFlagViews}}
<input type="submit" name="delete_user_flags" class="button is-warning" value="Delete selected flags" />
<p class="help">Note: normally it should not be needed to manually delete a flag because that's automatically handled by the system. So if it's necessary it means there's a bug that should be fixed.</p>
</form>
</div>
{{/hasFlags}}
<script>
$(() => {
document.getElementById("user_form").addEventListener('submit', function(event) {