Mastodon preferences
This commit is contained in:
parent
7b6e3700e4
commit
a5ffbec8d4
8 changed files with 176 additions and 6 deletions
|
|
@ -30,6 +30,9 @@ class User(BaseModel):
|
|||
mastodon_username = TextField(null=True)
|
||||
mastodon_access_token = TextField(null=True)
|
||||
|
||||
mastodon_attn_list = TextField(null=True)
|
||||
mastodon_post_public = BooleanField(null=True, default=False)
|
||||
|
||||
class Meta:
|
||||
table_name = 'user'
|
||||
|
||||
|
|
|
|||
|
|
@ -38,6 +38,15 @@ def user_mastodon_user_set(id, mastodon_username, mastodon_access_token):
|
|||
)
|
||||
return q.execute()
|
||||
|
||||
def user_preferences_set(id, mastodon_post_public, mastodon_attn_list):
|
||||
q = User.update(
|
||||
mastodon_attn_list=mastodon_attn_list,
|
||||
mastodon_post_public=mastodon_post_public
|
||||
).where(
|
||||
User.id == id
|
||||
)
|
||||
return q.execute()
|
||||
|
||||
def mastodon_server_get(name):
|
||||
return MastodonServer.get(name=name)
|
||||
|
||||
|
|
|
|||
28
flask/api.py
28
flask/api.py
|
|
@ -1,11 +1,14 @@
|
|||
import re
|
||||
import requests
|
||||
from functools import wraps
|
||||
from flask import Blueprint, jsonify, abort, request
|
||||
from flask_login import login_required, current_user
|
||||
from db.models import database, OrdersPool, Order, OrderAddOn, MastodonServer
|
||||
from db.queries import user_get, domsubusers_list, orders_pool_list, orders_pool, mastodon_server_get, mastodon_server_put, user_mastodon_server_set
|
||||
from db.queries import user_get, domsubusers_list, orders_pool_list, orders_pool, mastodon_server_get, mastodon_server_put, user_mastodon_server_set, user_preferences_set
|
||||
from settings import MASTODON_OAUTH_CLIENT_NAME, MASTODON_OAUTH_REDIRECT_URI, MASTODON_OAUTH_SCOPES, MASTODON_OAUTH_CLIENT_WEBSITE
|
||||
|
||||
RE_MASTODON_ATTN_LIST = re.compile(r'^(@(\w+)(@([\w\.]+))? )*(@\w+)(@[\w\.]+)?$')
|
||||
|
||||
api = Blueprint('api', __name__)
|
||||
|
||||
@api.route("/me")
|
||||
|
|
@ -17,9 +20,30 @@ def me():
|
|||
"username": user.telegram_username,
|
||||
"telegram_photo_url": user.telegram_photo_url,
|
||||
"mastodon_server": user.mastodon_server.name,
|
||||
"mastodon_username": user.mastodon_username
|
||||
"mastodon_username": user.mastodon_username,
|
||||
"mastodon_attn_list": user.mastodon_attn_list,
|
||||
"mastodon_post_public": user.mastodon_post_public
|
||||
})
|
||||
|
||||
@api.route("/profile", methods=["POST",])
|
||||
@login_required
|
||||
def profile():
|
||||
user = current_user.db_user
|
||||
|
||||
attn_list = request.json['attn_list'] if 'attn_list' in request.json else user.mastodon_attn_list
|
||||
post_public = request.json['post_public'] if 'post_public' in request.json else user.mastodon_post_public
|
||||
|
||||
if attn_list is not None and RE_MASTODON_ATTN_LIST.fullmatch(attn_list) is None:
|
||||
abort(500)
|
||||
|
||||
user_preferences_set(
|
||||
user.id,
|
||||
post_public,
|
||||
attn_list
|
||||
)
|
||||
|
||||
return ('', 204)
|
||||
|
||||
@api.route('/mastodon_oauth')
|
||||
@login_required
|
||||
def mastodon_oauth():
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import {
|
|||
Avatar,
|
||||
Box,
|
||||
Button,
|
||||
Checkbox,
|
||||
Fieldset,
|
||||
Flex,
|
||||
Modal,
|
||||
Paper,
|
||||
|
|
@ -11,8 +13,12 @@ import {
|
|||
Title,
|
||||
} from "@mantine/core";
|
||||
import { useDisclosure } from "@mantine/hooks";
|
||||
import { NavigateButton } from "./NavigateButton";
|
||||
import { Link } from "react-router";
|
||||
import { Link, useLoaderData } from "react-router";
|
||||
import { useForm } from "@mantine/form";
|
||||
import { notifications } from "@mantine/notifications";
|
||||
import { fetchHeaders } from "./fetch";
|
||||
|
||||
const RE_MASTODON_ACCOUNTS = /^(@(\w+)(@([\w\.]+))? )*(@\w+)(@[\w\.]+)?$/;
|
||||
|
||||
export const AuthorizeMastodonModal: React.FC<{
|
||||
opened: boolean;
|
||||
|
|
@ -46,9 +52,21 @@ export const AuthorizeMastodonModal: React.FC<{
|
|||
);
|
||||
};
|
||||
|
||||
type MastodonPreferencesForm = {
|
||||
attn_list?: string;
|
||||
post_public?: boolean;
|
||||
};
|
||||
|
||||
export const profileLoader = async () =>
|
||||
fetch(`/api/me`).then((response) => response.json());
|
||||
|
||||
export const Profile: React.FC = () => {
|
||||
const { username, telegram_photo_url, mastodon_server, mastodon_username } =
|
||||
useUserContext();
|
||||
const { mastodon_attn_list, mastodon_post_public } = useLoaderData<{
|
||||
mastodon_attn_list?: string;
|
||||
mastodon_post_public?: boolean;
|
||||
}>();
|
||||
const [opened, { open, close }] = useDisclosure(false);
|
||||
|
||||
const mastodon_account = React.useMemo(
|
||||
|
|
@ -56,6 +74,51 @@ export const Profile: React.FC = () => {
|
|||
[mastodon_server, mastodon_username],
|
||||
);
|
||||
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
const form = useForm<MastodonPreferencesForm>({
|
||||
mode: "uncontrolled",
|
||||
initialValues: {
|
||||
attn_list: mastodon_attn_list,
|
||||
post_public: mastodon_post_public,
|
||||
},
|
||||
validate: {
|
||||
attn_list: (value: string) =>
|
||||
!value || RE_MASTODON_ACCOUNTS.test(value)
|
||||
? null
|
||||
: "Please enter a valid list of accounts",
|
||||
},
|
||||
});
|
||||
|
||||
const handleSubmit = React.useCallback(
|
||||
form.onSubmit((values) => {
|
||||
setLoading(true);
|
||||
fetch(`/api/profile`, {
|
||||
method: "POST",
|
||||
headers: fetchHeaders(),
|
||||
body: JSON.stringify(values),
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.ok) {
|
||||
notifications.show({
|
||||
title: "Success",
|
||||
message: "Your preferences have been saved",
|
||||
color: "green",
|
||||
});
|
||||
} else {
|
||||
notifications.show({
|
||||
title: "Error",
|
||||
message: "There was a problem saving your preferences",
|
||||
color: "red",
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex align="center" gap="md" mb="xl">
|
||||
|
|
@ -70,6 +133,24 @@ export const Profile: React.FC = () => {
|
|||
<TextInput label="Account" w="50%" value={mastodon_account} />
|
||||
<Button onClick={open}>Authorize with Mastodon</Button>
|
||||
<AuthorizeMastodonModal opened={opened} onClose={close} />
|
||||
<Fieldset legend="Preferences" my="lg">
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Checkbox
|
||||
{...form.getInputProps("post_public", { type: "checkbox" })}
|
||||
label="Post orders publically"
|
||||
mb="md"
|
||||
/>
|
||||
<TextInput
|
||||
{...form.getInputProps("attn_list")}
|
||||
label="Tag Accounts"
|
||||
w="50%"
|
||||
description="Additional accounts to tag with your orders"
|
||||
/>
|
||||
<Button type="submit" loading={loading} mt="md">
|
||||
Save
|
||||
</Button>
|
||||
</form>
|
||||
</Fieldset>
|
||||
</Paper>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ export interface UserContextData {
|
|||
telegram_photo_url?: string;
|
||||
mastodon_server?: string;
|
||||
mastodon_username?: string;
|
||||
mastodon_attn_list?: string;
|
||||
mastodon_post_public?: boolean;
|
||||
}
|
||||
|
||||
const UserContext = React.createContext<UserContextData>({});
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import { SubOrderSets, subOrderSetsLoader } from "./SubOrderSets";
|
|||
import { OrderSet, orderSetLoader, orderSetAction } from "./OrderSet";
|
||||
import { UserContextProvider } from "./UserContext";
|
||||
import { Header } from "./Header";
|
||||
import { Profile } from "./Profile";
|
||||
import { Profile, profileLoader } from "./Profile";
|
||||
|
||||
const theme = createTheme({
|
||||
components: {
|
||||
|
|
@ -61,6 +61,7 @@ const router = createBrowserRouter([
|
|||
{
|
||||
path: "profile",
|
||||
Component: Profile,
|
||||
loader: profileLoader,
|
||||
},
|
||||
{
|
||||
path: "orders/:username",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"jsx": "react-jsx",
|
||||
"lib": ["ES2015", "DOM"],
|
||||
"lib": ["ES2018", "DOM"],
|
||||
"typeRoots": ["src/*.d.ts"],
|
||||
"esModuleInterop": true
|
||||
}
|
||||
|
|
|
|||
50
migrations/016_add_user_mastodon_attn_public.py
Normal file
50
migrations/016_add_user_mastodon_attn_public.py
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
"""Peewee migrations -- 016_add_user_mastodon_attn_public.py.
|
||||
|
||||
Some examples (model - class or model name)::
|
||||
|
||||
> Model = migrator.orm['table_name'] # Return model in current state by name
|
||||
> Model = migrator.ModelClass # Return model in current state by name
|
||||
|
||||
> migrator.sql(sql) # Run custom SQL
|
||||
> migrator.run(func, *args, **kwargs) # Run python function with the given args
|
||||
> migrator.create_model(Model) # Create a model (could be used as decorator)
|
||||
> migrator.remove_model(model, cascade=True) # Remove a model
|
||||
> migrator.add_fields(model, **fields) # Add fields to a model
|
||||
> migrator.change_fields(model, **fields) # Change fields
|
||||
> migrator.remove_fields(model, *field_names, cascade=True)
|
||||
> migrator.rename_field(model, old_field_name, new_field_name)
|
||||
> migrator.rename_table(model, new_table_name)
|
||||
> migrator.add_index(model, *col_names, unique=False)
|
||||
> migrator.add_not_null(model, *field_names)
|
||||
> migrator.add_default(model, field_name, default)
|
||||
> migrator.add_constraint(model, name, sql)
|
||||
> migrator.drop_index(model, *col_names)
|
||||
> migrator.drop_not_null(model, *field_names)
|
||||
> migrator.drop_constraints(model, *constraints)
|
||||
|
||||
"""
|
||||
|
||||
from contextlib import suppress
|
||||
|
||||
import peewee as pw
|
||||
from peewee_migrate import Migrator
|
||||
|
||||
|
||||
with suppress(ImportError):
|
||||
import playhouse.postgres_ext as pw_pext
|
||||
|
||||
|
||||
def migrate(migrator: Migrator, database: pw.Database, *, fake=False):
|
||||
"""Write your migrations here."""
|
||||
|
||||
migrator.add_fields(
|
||||
'user',
|
||||
|
||||
mastodon_attn_list=pw.TextField(null=True),
|
||||
mastodon_post_public=pw.BooleanField(default=False, null=True))
|
||||
|
||||
|
||||
def rollback(migrator: Migrator, database: pw.Database, *, fake=False):
|
||||
"""Write your rollback migrations here."""
|
||||
|
||||
migrator.remove_fields('user', 'mastodon_attn_list', 'mastodon_post_public')
|
||||
Loading…
Reference in a new issue