Merge branch 'flutter_app'
This commit is contained in:
commit
dac268f40b
2
flutter/.gitignore
vendored
2
flutter/.gitignore
vendored
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
*.keystore
|
*.keystore
|
||||||
|
|
||||||
|
firepit-log.txt
|
||||||
|
flutter_jank_*
|
||||||
|
|
||||||
|
|
||||||
#######################################################################################################################
|
#######################################################################################################################
|
||||||
|
@ -7,6 +7,9 @@ run:
|
|||||||
test:
|
test:
|
||||||
dart analyze
|
dart analyze
|
||||||
|
|
||||||
|
fix:
|
||||||
|
dart fix --apply
|
||||||
|
|
||||||
gen:
|
gen:
|
||||||
dart run build_runner build
|
dart run build_runner build
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
pid="$( pgrep -f 'flutter_tools\.[s]napshot run' || echo '' )"
|
pid="$( pgrep -f 'flutter_tools\.[s]napshot run' || echo '' | tail -n 1 )"
|
||||||
|
|
||||||
if [ -z "$pid" ]; then
|
if [ -z "$pid" ]; then
|
||||||
red "No [flutter run] process found - exiting"
|
red "No [flutter run] process found - exiting"
|
||||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -7,7 +7,6 @@ import 'package:simplecloudnotifier/models/client.dart';
|
|||||||
import 'package:simplecloudnotifier/models/keytoken.dart';
|
import 'package:simplecloudnotifier/models/keytoken.dart';
|
||||||
import 'package:simplecloudnotifier/models/subscription.dart';
|
import 'package:simplecloudnotifier/models/subscription.dart';
|
||||||
import 'package:simplecloudnotifier/models/user.dart';
|
import 'package:simplecloudnotifier/models/user.dart';
|
||||||
import 'package:simplecloudnotifier/state/app_auth.dart';
|
|
||||||
import 'package:simplecloudnotifier/state/application_log.dart';
|
import 'package:simplecloudnotifier/state/application_log.dart';
|
||||||
import 'package:simplecloudnotifier/state/globals.dart';
|
import 'package:simplecloudnotifier/state/globals.dart';
|
||||||
import 'package:simplecloudnotifier/state/request_log.dart';
|
import 'package:simplecloudnotifier/state/request_log.dart';
|
||||||
@ -182,6 +181,16 @@ class APIClient {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future<ChannelWithSubscription> getChannel(TokenSource auth, String cid) async {
|
||||||
|
return await _request(
|
||||||
|
name: 'getChannel',
|
||||||
|
method: 'GET',
|
||||||
|
relURL: 'users/${auth.getUserID()}/channels/${cid}',
|
||||||
|
fn: ChannelWithSubscription.fromJson,
|
||||||
|
authToken: auth.getToken(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
static Future<(String, List<Message>)> getMessageList(TokenSource auth, String pageToken, {int? pageSize, List<String>? channelIDs}) async {
|
static Future<(String, List<Message>)> getMessageList(TokenSource auth, String pageToken, {int? pageSize, List<String>? channelIDs}) async {
|
||||||
return await _request(
|
return await _request(
|
||||||
name: 'getMessageList',
|
name: 'getMessageList',
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
class APIException implements Exception {
|
class APIException implements Exception {
|
||||||
final int httpStatus;
|
final int httpStatus;
|
||||||
final String error;
|
final int error;
|
||||||
final String errHighlight;
|
final String errHighlight;
|
||||||
final String message;
|
final String message;
|
||||||
|
|
||||||
|
@ -11,46 +11,64 @@ class SCNAppBar extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
required this.showThemeSwitch,
|
required this.showThemeSwitch,
|
||||||
required this.showDebug,
|
required this.showDebug,
|
||||||
required this.showSearch,
|
required this.showSearch,
|
||||||
|
required this.showShare,
|
||||||
|
this.onShare = null,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
final String? title;
|
final String? title;
|
||||||
final bool showThemeSwitch;
|
final bool showThemeSwitch;
|
||||||
final bool showDebug;
|
final bool showDebug;
|
||||||
final bool showSearch;
|
final bool showSearch;
|
||||||
|
final bool showShare;
|
||||||
|
final void Function()? onShare;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return AppBar(
|
var actions = <Widget>[];
|
||||||
title: Text(title ?? 'Simple Cloud Notifier 2.0'),
|
|
||||||
actions: <Widget>[
|
if (showThemeSwitch) {
|
||||||
if (showThemeSwitch)
|
actions.add(Consumer<AppTheme>(
|
||||||
Consumer<AppTheme>(
|
|
||||||
builder: (context, appTheme, child) => IconButton(
|
builder: (context, appTheme, child) => IconButton(
|
||||||
icon: Icon(appTheme.darkMode ? FontAwesomeIcons.solidSun : FontAwesomeIcons.solidMoon),
|
icon: Icon(appTheme.darkMode ? FontAwesomeIcons.solidSun : FontAwesomeIcons.solidMoon),
|
||||||
tooltip: 'Debug',
|
tooltip: appTheme.darkMode ? 'Light mode' : 'Dark mode',
|
||||||
onPressed: () {
|
onPressed: appTheme.switchDarkMode,
|
||||||
appTheme.switchDarkMode();
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
));
|
||||||
if (!showThemeSwitch) SizedBox.square(dimension: 40),
|
} else {
|
||||||
if (showDebug)
|
actions.add(SizedBox.square(dimension: 40));
|
||||||
IconButton(
|
}
|
||||||
|
|
||||||
|
if (showDebug) {
|
||||||
|
actions.add(IconButton(
|
||||||
icon: const Icon(FontAwesomeIcons.solidSpiderBlackWidow),
|
icon: const Icon(FontAwesomeIcons.solidSpiderBlackWidow),
|
||||||
tooltip: 'Debug',
|
tooltip: 'Debug',
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.push(context, MaterialPageRoute<DebugMainPage>(builder: (context) => DebugMainPage()));
|
Navigator.push(context, MaterialPageRoute<DebugMainPage>(builder: (context) => DebugMainPage()));
|
||||||
},
|
},
|
||||||
),
|
));
|
||||||
if (!showDebug) SizedBox.square(dimension: 40),
|
} else {
|
||||||
if (showSearch)
|
actions.add(SizedBox.square(dimension: 40));
|
||||||
IconButton(
|
}
|
||||||
|
|
||||||
|
if (showSearch) {
|
||||||
|
actions.add(IconButton(
|
||||||
icon: const Icon(FontAwesomeIcons.solidMagnifyingGlass),
|
icon: const Icon(FontAwesomeIcons.solidMagnifyingGlass),
|
||||||
tooltip: 'Search',
|
tooltip: 'Search',
|
||||||
onPressed: () {},
|
onPressed: () {/*TODO*/},
|
||||||
),
|
));
|
||||||
if (!showSearch) SizedBox.square(dimension: 40),
|
} else if (showShare) {
|
||||||
],
|
actions.add(IconButton(
|
||||||
|
icon: const Icon(FontAwesomeIcons.solidShareNodes),
|
||||||
|
tooltip: 'Share',
|
||||||
|
onPressed: onShare ?? () {},
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
actions.add(SizedBox.square(dimension: 40));
|
||||||
|
}
|
||||||
|
|
||||||
|
return AppBar(
|
||||||
|
title: Text(title ?? 'Simple Cloud Notifier 2.0'),
|
||||||
|
actions: actions,
|
||||||
backgroundColor: Theme.of(context).secondaryHeaderColor,
|
backgroundColor: Theme.of(context).secondaryHeaderColor,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,8 @@ class SCNScaffold extends StatelessWidget {
|
|||||||
this.showThemeSwitch = true,
|
this.showThemeSwitch = true,
|
||||||
this.showDebug = true,
|
this.showDebug = true,
|
||||||
this.showSearch = true,
|
this.showSearch = true,
|
||||||
|
this.showShare = false,
|
||||||
|
this.onShare = null,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
final Widget child;
|
final Widget child;
|
||||||
@ -16,6 +18,8 @@ class SCNScaffold extends StatelessWidget {
|
|||||||
final bool showThemeSwitch;
|
final bool showThemeSwitch;
|
||||||
final bool showDebug;
|
final bool showDebug;
|
||||||
final bool showSearch;
|
final bool showSearch;
|
||||||
|
final bool showShare;
|
||||||
|
final void Function()? onShare;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -25,6 +29,8 @@ class SCNScaffold extends StatelessWidget {
|
|||||||
showThemeSwitch: showThemeSwitch,
|
showThemeSwitch: showThemeSwitch,
|
||||||
showDebug: showDebug,
|
showDebug: showDebug,
|
||||||
showSearch: showSearch,
|
showSearch: showSearch,
|
||||||
|
showShare: showShare,
|
||||||
|
onShare: onShare ?? () {},
|
||||||
),
|
),
|
||||||
body: child,
|
body: child,
|
||||||
);
|
);
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
@ -17,15 +19,24 @@ import 'firebase_options.dart';
|
|||||||
void main() async {
|
void main() async {
|
||||||
print('[INIT] Application starting...');
|
print('[INIT] Application starting...');
|
||||||
|
|
||||||
|
print('[INIT] Ensure WidgetsFlutterBinding...');
|
||||||
|
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
await Hive.initFlutter();
|
print('[INIT] Init Globals...');
|
||||||
|
|
||||||
await Globals().init();
|
await Globals().init();
|
||||||
|
|
||||||
|
print('[INIT] Init Hive...');
|
||||||
|
|
||||||
|
await Hive.initFlutter();
|
||||||
|
|
||||||
Hive.registerAdapter(SCNRequestAdapter());
|
Hive.registerAdapter(SCNRequestAdapter());
|
||||||
Hive.registerAdapter(SCNLogAdapter());
|
Hive.registerAdapter(SCNLogAdapter());
|
||||||
Hive.registerAdapter(SCNLogLevelAdapter());
|
Hive.registerAdapter(SCNLogLevelAdapter());
|
||||||
|
|
||||||
|
print('[INIT] Load Hive<scn-requests>...');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await Hive.openBox<SCNRequest>('scn-requests');
|
await Hive.openBox<SCNRequest>('scn-requests');
|
||||||
} catch (exc, trace) {
|
} catch (exc, trace) {
|
||||||
@ -34,6 +45,8 @@ void main() async {
|
|||||||
ApplicationLog.error('Failed to open Hive-Box: scn-requests: ' + exc.toString(), trace: trace);
|
ApplicationLog.error('Failed to open Hive-Box: scn-requests: ' + exc.toString(), trace: trace);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
print('[INIT] Load Hive<scn-logs>...');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await Hive.openBox<SCNLog>('scn-logs');
|
await Hive.openBox<SCNLog>('scn-logs');
|
||||||
} catch (exc, trace) {
|
} catch (exc, trace) {
|
||||||
@ -42,23 +55,32 @@ void main() async {
|
|||||||
ApplicationLog.error('Failed to open Hive-Box: scn-logs: ' + exc.toString(), trace: trace);
|
ApplicationLog.error('Failed to open Hive-Box: scn-logs: ' + exc.toString(), trace: trace);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
print('[INIT] Load AppAuth...');
|
||||||
|
|
||||||
final appAuth = AppAuth(); // ensure UserAccount is loaded
|
final appAuth = AppAuth(); // ensure UserAccount is loaded
|
||||||
|
|
||||||
if (appAuth.isAuth()) {
|
if (appAuth.isAuth()) {
|
||||||
try {
|
try {
|
||||||
|
print('[INIT] Load User...');
|
||||||
await appAuth.loadUser();
|
await appAuth.loadUser();
|
||||||
|
//TODO fallback to cached user (perhaps event use cached client (if exists) directly and only update/load in background)
|
||||||
} catch (exc, trace) {
|
} catch (exc, trace) {
|
||||||
ApplicationLog.error('Failed to load user (on startup): ' + exc.toString(), trace: trace);
|
ApplicationLog.error('Failed to load user (on startup): ' + exc.toString(), trace: trace);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
print('[INIT] Load Client...');
|
||||||
await appAuth.loadClient();
|
await appAuth.loadClient();
|
||||||
|
//TODO fallback to cached client (perhaps event use cached client (if exists) directly and only update/load in background)
|
||||||
} catch (exc, trace) {
|
} catch (exc, trace) {
|
||||||
ApplicationLog.error('Failed to load user (on startup): ' + exc.toString(), trace: trace);
|
ApplicationLog.error('Failed to load user (on startup): ' + exc.toString(), trace: trace);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!Platform.isLinux) {
|
||||||
|
print('[INIT] Init Firebase...');
|
||||||
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
|
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
|
||||||
|
|
||||||
|
print('[INIT] Request Notification permissions...');
|
||||||
await FirebaseMessaging.instance.requestPermission(provisional: true);
|
await FirebaseMessaging.instance.requestPermission(provisional: true);
|
||||||
|
|
||||||
FirebaseMessaging.instance.onTokenRefresh.listen((fcmToken) {
|
FirebaseMessaging.instance.onTokenRefresh.listen((fcmToken) {
|
||||||
@ -72,6 +94,7 @@ void main() async {
|
|||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
print('[INIT] Query firebase token...');
|
||||||
final fcmToken = await FirebaseMessaging.instance.getToken();
|
final fcmToken = await FirebaseMessaging.instance.getToken();
|
||||||
if (fcmToken != null) {
|
if (fcmToken != null) {
|
||||||
setFirebaseToken(fcmToken);
|
setFirebaseToken(fcmToken);
|
||||||
@ -79,8 +102,11 @@ void main() async {
|
|||||||
} catch (exc, trace) {
|
} catch (exc, trace) {
|
||||||
ApplicationLog.error('Failed to get+set firebase token: ' + exc.toString(), trace: trace);
|
ApplicationLog.error('Failed to get+set firebase token: ' + exc.toString(), trace: trace);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
print('[INIT] Skip Firebase init (Platform == Linux)...');
|
||||||
|
}
|
||||||
|
|
||||||
ApplicationLog.debug('Application started');
|
ApplicationLog.debug('[INIT] Application started');
|
||||||
|
|
||||||
runApp(
|
runApp(
|
||||||
MultiProvider(
|
MultiProvider(
|
||||||
@ -112,7 +138,7 @@ void setFirebaseToken(String fcmToken) async {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (oldToken != null && oldToken == fcmToken && client != null && client!.fcmToken == fcmToken) {
|
if (oldToken != null && oldToken == fcmToken && client != null && client.fcmToken == fcmToken) {
|
||||||
ApplicationLog.info('Firebase token unchanged - do nothing', additional: 'Token: $fcmToken');
|
ApplicationLog.info('Firebase token unchanged - do nothing', additional: 'Token: $fcmToken');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,61 @@
|
|||||||
class APIError {
|
class APIError {
|
||||||
final String success;
|
final bool success;
|
||||||
final String error;
|
final int error;
|
||||||
final String errhighlight;
|
final String errhighlight;
|
||||||
final String message;
|
final String message;
|
||||||
|
|
||||||
|
static final MISSING_UID = 1101;
|
||||||
|
static final MISSING_TOK = 1102;
|
||||||
|
static final MISSING_TITLE = 1103;
|
||||||
|
static final INVALID_PRIO = 1104;
|
||||||
|
static final REQ_METHOD = 1105;
|
||||||
|
static final INVALID_CLIENTTYPE = 1106;
|
||||||
|
static final PAGETOKEN_ERROR = 1121;
|
||||||
|
static final BINDFAIL_QUERY_PARAM = 1151;
|
||||||
|
static final BINDFAIL_BODY_PARAM = 1152;
|
||||||
|
static final BINDFAIL_URI_PARAM = 1153;
|
||||||
|
static final INVALID_BODY_PARAM = 1161;
|
||||||
|
static final INVALID_ENUM_VALUE = 1171;
|
||||||
|
|
||||||
|
static final NO_TITLE = 1201;
|
||||||
|
static final TITLE_TOO_LONG = 1202;
|
||||||
|
static final CONTENT_TOO_LONG = 1203;
|
||||||
|
static final USR_MSG_ID_TOO_LONG = 1204;
|
||||||
|
static final TIMESTAMP_OUT_OF_RANGE = 1205;
|
||||||
|
static final SENDERNAME_TOO_LONG = 1206;
|
||||||
|
static final CHANNEL_TOO_LONG = 1207;
|
||||||
|
static final CHANNEL_DESCRIPTION_TOO_LONG = 1208;
|
||||||
|
static final CHANNEL_NAME_EMPTY = 1209;
|
||||||
|
|
||||||
|
static final USER_NOT_FOUND = 1301;
|
||||||
|
static final CLIENT_NOT_FOUND = 1302;
|
||||||
|
static final CHANNEL_NOT_FOUND = 1303;
|
||||||
|
static final SUBSCRIPTION_NOT_FOUND = 1304;
|
||||||
|
static final MESSAGE_NOT_FOUND = 1305;
|
||||||
|
static final SUBSCRIPTION_USER_MISMATCH = 1306;
|
||||||
|
static final KEY_NOT_FOUND = 1307;
|
||||||
|
static final USER_AUTH_FAILED = 1311;
|
||||||
|
|
||||||
|
static final NO_DEVICE_LINKED = 1401;
|
||||||
|
|
||||||
|
static final CHANNEL_ALREADY_EXISTS = 1501;
|
||||||
|
static final CANNOT_SELFDELETE_KEY = 1511;
|
||||||
|
static final CANNOT_SELFUPDATE_KEY = 1512;
|
||||||
|
|
||||||
|
static final QUOTA_REACHED = 2101;
|
||||||
|
|
||||||
|
static final FAILED_VERIFY_PRO_TOKEN = 3001;
|
||||||
|
static final INVALID_PRO_TOKEN = 3002;
|
||||||
|
|
||||||
|
static final COMMIT_FAILED = 9001;
|
||||||
|
static final DATABASE_ERROR = 9002;
|
||||||
|
static final PERM_QUERY_FAIL = 9003;
|
||||||
|
static final FIREBASE_COM_FAILED = 9901;
|
||||||
|
static final FIREBASE_COM_ERRORED = 9902;
|
||||||
|
static final INTERNAL_EXCEPTION = 9903;
|
||||||
|
static final PANIC = 9904;
|
||||||
|
static final NOT_IMPLEMENTED = 9905;
|
||||||
|
|
||||||
const APIError({
|
const APIError({
|
||||||
required this.success,
|
required this.success,
|
||||||
required this.error,
|
required this.error,
|
||||||
@ -13,8 +65,8 @@ class APIError {
|
|||||||
|
|
||||||
factory APIError.fromJson(Map<String, dynamic> json) {
|
factory APIError.fromJson(Map<String, dynamic> json) {
|
||||||
return APIError(
|
return APIError(
|
||||||
success: json['success'] as String,
|
success: json['success'] as bool,
|
||||||
error: json['error'] as String,
|
error: (json['error'] as double).toInt(),
|
||||||
errhighlight: json['errhighlight'] as String,
|
errhighlight: json['errhighlight'] as String,
|
||||||
message: json['message'] as String,
|
message: json['message'] as String,
|
||||||
);
|
);
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_lazy_indexed_stack/flutter_lazy_indexed_stack.dart';
|
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:simplecloudnotifier/components/hidable_fab/hidable_fab.dart';
|
import 'package:simplecloudnotifier/components/hidable_fab/hidable_fab.dart';
|
||||||
@ -62,15 +61,16 @@ class _SCNNavLayoutState extends State<SCNNavLayout> {
|
|||||||
title: null,
|
title: null,
|
||||||
showDebug: true,
|
showDebug: true,
|
||||||
showSearch: _selectedIndex == 0 || _selectedIndex == 1,
|
showSearch: _selectedIndex == 0 || _selectedIndex == 1,
|
||||||
|
showShare: false,
|
||||||
showThemeSwitch: true,
|
showThemeSwitch: true,
|
||||||
),
|
),
|
||||||
body: LazyIndexedStack(
|
body: IndexedStack(
|
||||||
children: [
|
children: [
|
||||||
MessageListPage(),
|
ExcludeFocus(excluding: _selectedIndex != 0, child: MessageListPage()),
|
||||||
ChannelRootPage(),
|
ExcludeFocus(excluding: _selectedIndex != 1, child: ChannelRootPage()),
|
||||||
AccountRootPage(),
|
ExcludeFocus(excluding: _selectedIndex != 2, child: AccountRootPage()),
|
||||||
SettingsRootPage(),
|
ExcludeFocus(excluding: _selectedIndex != 3, child: SettingsRootPage()),
|
||||||
SendRootPage(),
|
ExcludeFocus(excluding: _selectedIndex != 4, child: SendRootPage()),
|
||||||
],
|
],
|
||||||
index: _selectedIndex,
|
index: _selectedIndex,
|
||||||
),
|
),
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
@ -9,6 +11,8 @@ import 'package:simplecloudnotifier/state/application_log.dart';
|
|||||||
import 'package:simplecloudnotifier/state/globals.dart';
|
import 'package:simplecloudnotifier/state/globals.dart';
|
||||||
import 'package:simplecloudnotifier/state/app_auth.dart';
|
import 'package:simplecloudnotifier/state/app_auth.dart';
|
||||||
import 'package:simplecloudnotifier/utils/toaster.dart';
|
import 'package:simplecloudnotifier/utils/toaster.dart';
|
||||||
|
import 'package:simplecloudnotifier/utils/ui.dart';
|
||||||
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
class AccountRootPage extends StatefulWidget {
|
class AccountRootPage extends StatefulWidget {
|
||||||
const AccountRootPage({super.key});
|
const AccountRootPage({super.key});
|
||||||
@ -140,22 +144,23 @@ class _AccountRootPageState extends State<AccountRootPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 32),
|
const SizedBox(height: 32),
|
||||||
FilledButton(
|
UI.button(
|
||||||
style: FilledButton.styleFrom(textStyle: const TextStyle(fontSize: 24), padding: const EdgeInsets.fromLTRB(8, 12, 8, 12)),
|
text: 'Create new account',
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (loading) return;
|
if (loading) return;
|
||||||
_createNewAccount();
|
_createNewAccount();
|
||||||
},
|
},
|
||||||
child: const Text('Create new account'),
|
big: true,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
FilledButton.tonal(
|
UI.button(
|
||||||
style: FilledButton.styleFrom(textStyle: const TextStyle(fontSize: 24), padding: const EdgeInsets.fromLTRB(8, 12, 8, 12)),
|
text: 'Use existing account',
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (loading) return;
|
if (loading) return;
|
||||||
Navigator.push(context, MaterialPageRoute<AccountLoginPage>(builder: (context) => AccountLoginPage()));
|
Navigator.push(context, MaterialPageRoute<AccountLoginPage>(builder: (context) => AccountLoginPage()));
|
||||||
},
|
},
|
||||||
child: const Text('Use existing account'),
|
tonal: true,
|
||||||
|
big: true,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -164,11 +169,7 @@ class _AccountRootPageState extends State<AccountRootPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildShowAccount(BuildContext context, AppAuth acc, User user) {
|
Widget _buildShowAccount(BuildContext context, AppAuth acc, User user) {
|
||||||
//TODO better layout
|
return SingleChildScrollView(
|
||||||
return Column(
|
|
||||||
children: [
|
|
||||||
SingleChildScrollView(
|
|
||||||
scrollDirection: Axis.vertical,
|
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(8.0, 24.0, 8.0, 8.0),
|
padding: const EdgeInsets.fromLTRB(8.0, 24.0, 8.0, 8.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
@ -178,14 +179,12 @@ class _AccountRootPageState extends State<AccountRootPage> {
|
|||||||
Text(user.username ?? user.userID, overflow: TextOverflow.ellipsis, style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20)),
|
Text(user.username ?? user.userID, overflow: TextOverflow.ellipsis, style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20)),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
..._buildCards(context, user),
|
..._buildCards(context, user),
|
||||||
],
|
SizedBox(height: 16),
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Expanded(child: SizedBox(height: 16)),
|
|
||||||
_buildFooter(context, user),
|
_buildFooter(context, user),
|
||||||
SizedBox(height: 40)
|
SizedBox(height: 40),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -272,23 +271,15 @@ class _AccountRootPageState extends State<AccountRootPage> {
|
|||||||
Column(
|
Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
IconButton(
|
UI.buttonIconOnly(
|
||||||
icon: FaIcon(FontAwesomeIcons.pen),
|
|
||||||
iconSize: 18,
|
|
||||||
padding: EdgeInsets.all(4),
|
|
||||||
constraints: BoxConstraints(),
|
|
||||||
style: ButtonStyle(tapTargetSize: MaterialTapTargetSize.shrinkWrap),
|
|
||||||
onPressed: () {/*TODO*/},
|
onPressed: () {/*TODO*/},
|
||||||
|
icon: FontAwesomeIcons.pen,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
if (!user.isPro)
|
if (!user.isPro)
|
||||||
IconButton(
|
UI.buttonIconOnly(
|
||||||
icon: FaIcon(FontAwesomeIcons.cartCircleArrowUp),
|
|
||||||
iconSize: 18,
|
|
||||||
padding: EdgeInsets.all(4),
|
|
||||||
constraints: BoxConstraints(),
|
|
||||||
style: ButtonStyle(tapTargetSize: MaterialTapTargetSize.shrinkWrap),
|
|
||||||
onPressed: () {/*TODO*/},
|
onPressed: () {/*TODO*/},
|
||||||
|
icon: FontAwesomeIcons.cartCircleArrowUp,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -298,15 +289,9 @@ class _AccountRootPageState extends State<AccountRootPage> {
|
|||||||
|
|
||||||
List<Widget> _buildCards(BuildContext context, User user) {
|
List<Widget> _buildCards(BuildContext context, User user) {
|
||||||
return [
|
return [
|
||||||
Card.filled(
|
UI.buttonCard(
|
||||||
|
context: context,
|
||||||
margin: EdgeInsets.fromLTRB(0, 4, 0, 4),
|
margin: EdgeInsets.fromLTRB(0, 4, 0, 4),
|
||||||
shape: BeveledRectangleBorder(borderRadius: BorderRadius.circular(0)),
|
|
||||||
color: Theme.of(context).cardTheme.color,
|
|
||||||
child: InkWell(
|
|
||||||
splashColor: Theme.of(context).splashColor,
|
|
||||||
onTap: () {/*TODO*/},
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
FutureBuilder(
|
FutureBuilder(
|
||||||
@ -322,18 +307,11 @@ class _AccountRootPageState extends State<AccountRootPage> {
|
|||||||
Text('Subscriptions', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20)),
|
Text('Subscriptions', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Card.filled(
|
|
||||||
margin: EdgeInsets.fromLTRB(0, 4, 0, 4),
|
|
||||||
shape: BeveledRectangleBorder(borderRadius: BorderRadius.circular(0)),
|
|
||||||
color: Theme.of(context).cardTheme.color,
|
|
||||||
child: InkWell(
|
|
||||||
splashColor: Theme.of(context).splashColor,
|
|
||||||
onTap: () {/*TODO*/},
|
onTap: () {/*TODO*/},
|
||||||
child: Padding(
|
),
|
||||||
padding: const EdgeInsets.all(16),
|
UI.buttonCard(
|
||||||
|
context: context,
|
||||||
|
margin: EdgeInsets.fromLTRB(0, 4, 0, 4),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
FutureBuilder(
|
FutureBuilder(
|
||||||
@ -349,18 +327,11 @@ class _AccountRootPageState extends State<AccountRootPage> {
|
|||||||
Text('Clients', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20)),
|
Text('Clients', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Card.filled(
|
|
||||||
margin: EdgeInsets.fromLTRB(0, 4, 0, 4),
|
|
||||||
shape: BeveledRectangleBorder(borderRadius: BorderRadius.circular(0)),
|
|
||||||
color: Theme.of(context).cardTheme.color,
|
|
||||||
child: InkWell(
|
|
||||||
splashColor: Theme.of(context).splashColor,
|
|
||||||
onTap: () {/*TODO*/},
|
onTap: () {/*TODO*/},
|
||||||
child: Padding(
|
),
|
||||||
padding: const EdgeInsets.all(16),
|
UI.buttonCard(
|
||||||
|
context: context,
|
||||||
|
margin: EdgeInsets.fromLTRB(0, 4, 0, 4),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
FutureBuilder(
|
FutureBuilder(
|
||||||
@ -376,18 +347,11 @@ class _AccountRootPageState extends State<AccountRootPage> {
|
|||||||
Text('Keys', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20)),
|
Text('Keys', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Card.filled(
|
|
||||||
margin: EdgeInsets.fromLTRB(0, 4, 0, 4),
|
|
||||||
shape: BeveledRectangleBorder(borderRadius: BorderRadius.circular(0)),
|
|
||||||
color: Theme.of(context).cardTheme.color,
|
|
||||||
child: InkWell(
|
|
||||||
splashColor: Theme.of(context).splashColor,
|
|
||||||
onTap: () {/*TODO*/},
|
onTap: () {/*TODO*/},
|
||||||
child: Padding(
|
),
|
||||||
padding: const EdgeInsets.all(16),
|
UI.buttonCard(
|
||||||
|
context: context,
|
||||||
|
margin: EdgeInsets.fromLTRB(0, 4, 0, 4),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
FutureBuilder(
|
FutureBuilder(
|
||||||
@ -403,18 +367,11 @@ class _AccountRootPageState extends State<AccountRootPage> {
|
|||||||
Text('Channels', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20)),
|
Text('Channels', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Card.filled(
|
|
||||||
margin: EdgeInsets.fromLTRB(0, 4, 0, 4),
|
|
||||||
shape: BeveledRectangleBorder(borderRadius: BorderRadius.circular(0)),
|
|
||||||
color: Theme.of(context).cardTheme.color,
|
|
||||||
child: InkWell(
|
|
||||||
splashColor: Theme.of(context).splashColor,
|
|
||||||
onTap: () {/*TODO*/},
|
onTap: () {/*TODO*/},
|
||||||
child: Padding(
|
),
|
||||||
padding: const EdgeInsets.all(16),
|
UI.buttonCard(
|
||||||
|
context: context,
|
||||||
|
margin: EdgeInsets.fromLTRB(0, 4, 0, 4),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Text('${user.messagesSent}', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20)),
|
Text('${user.messagesSent}', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20)),
|
||||||
@ -422,8 +379,7 @@ class _AccountRootPageState extends State<AccountRootPage> {
|
|||||||
Text('Messages', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20)),
|
Text('Messages', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
onTap: () {/*TODO*/},
|
||||||
),
|
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -433,9 +389,19 @@ class _AccountRootPageState extends State<AccountRootPage> {
|
|||||||
padding: const EdgeInsets.fromLTRB(8, 0, 8, 0),
|
padding: const EdgeInsets.fromLTRB(8, 0, 8, 0),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(child: FilledButton(onPressed: _logout, child: Text('Logout'), style: TextButton.styleFrom(backgroundColor: Colors.orange))),
|
Expanded(
|
||||||
|
child: UI.button(
|
||||||
|
text: 'Logout',
|
||||||
|
onPressed: _logout,
|
||||||
|
color: Colors.orange,
|
||||||
|
)),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Expanded(child: FilledButton(onPressed: _deleteAccount, child: Text('Delete Account'), style: TextButton.styleFrom(backgroundColor: Colors.red))),
|
Expanded(
|
||||||
|
child: UI.button(
|
||||||
|
text: 'Delete Account',
|
||||||
|
onPressed: _deleteAccount,
|
||||||
|
color: Colors.red,
|
||||||
|
)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -447,6 +413,11 @@ class _AccountRootPageState extends State<AccountRootPage> {
|
|||||||
final acc = Provider.of<AppAuth>(context, listen: false);
|
final acc = Provider.of<AppAuth>(context, listen: false);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
final String? fcmToken;
|
||||||
|
if (Platform.isLinux) {
|
||||||
|
Toaster.warn("Unsupported Platform", 'Your platform is not supported by FCM - notifications will not work');
|
||||||
|
fcmToken = '(linux-' + Uuid().v4() + ')';
|
||||||
|
} else {
|
||||||
final notificationSettings = await FirebaseMessaging.instance.requestPermission(provisional: true);
|
final notificationSettings = await FirebaseMessaging.instance.requestPermission(provisional: true);
|
||||||
|
|
||||||
if (notificationSettings.authorizationStatus == AuthorizationStatus.denied) {
|
if (notificationSettings.authorizationStatus == AuthorizationStatus.denied) {
|
||||||
@ -454,7 +425,8 @@ class _AccountRootPageState extends State<AccountRootPage> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final fcmToken = await FirebaseMessaging.instance.getToken();
|
fcmToken = await FirebaseMessaging.instance.getToken();
|
||||||
|
}
|
||||||
|
|
||||||
if (fcmToken == null) {
|
if (fcmToken == null) {
|
||||||
Toaster.warn("Missing Token", 'No FCM Token found, please allow notifications, ensure you have a network connection and restart the app');
|
Toaster.warn("Missing Token", 'No FCM Token found, please allow notifications, ensure you have a network connection and restart the app');
|
||||||
|
@ -9,6 +9,7 @@ import 'package:simplecloudnotifier/state/globals.dart';
|
|||||||
import 'package:simplecloudnotifier/state/app_auth.dart';
|
import 'package:simplecloudnotifier/state/app_auth.dart';
|
||||||
import 'package:simplecloudnotifier/state/token_source.dart';
|
import 'package:simplecloudnotifier/state/token_source.dart';
|
||||||
import 'package:simplecloudnotifier/utils/toaster.dart';
|
import 'package:simplecloudnotifier/utils/toaster.dart';
|
||||||
|
import 'package:simplecloudnotifier/utils/ui.dart';
|
||||||
|
|
||||||
class AccountLoginPage extends StatefulWidget {
|
class AccountLoginPage extends StatefulWidget {
|
||||||
const AccountLoginPage({super.key});
|
const AccountLoginPage({super.key});
|
||||||
@ -102,10 +103,10 @@ class _AccountLoginPageState extends State<AccountLoginPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
FilledButton(
|
UI.button(
|
||||||
style: FilledButton.styleFrom(textStyle: const TextStyle(fontSize: 24), padding: const EdgeInsets.fromLTRB(8, 12, 8, 12)),
|
text: 'Login',
|
||||||
|
big: true,
|
||||||
onPressed: _login,
|
onPressed: _login,
|
||||||
child: const Text('Login'),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:simplecloudnotifier/utils/toaster.dart';
|
import 'package:simplecloudnotifier/utils/toaster.dart';
|
||||||
import 'package:toastification/toastification.dart';
|
import 'package:simplecloudnotifier/utils/ui.dart';
|
||||||
|
|
||||||
class DebugActionsPage extends StatefulWidget {
|
class DebugActionsPage extends StatefulWidget {
|
||||||
@override
|
@override
|
||||||
@ -17,36 +17,40 @@ class _DebugActionsPageState extends State<DebugActionsPage> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
FilledButton(
|
UI.button(
|
||||||
style: FilledButton.styleFrom(textStyle: const TextStyle(fontSize: 20)),
|
big: false,
|
||||||
onPressed: () => Toaster.success("Hello World", "This was a triumph!"),
|
onPressed: () => Toaster.success("Hello World", "This was a triumph!"),
|
||||||
child: const Text('Show Success Notification'),
|
text: 'Show Success Notification',
|
||||||
),
|
),
|
||||||
FilledButton(
|
SizedBox(height: 4),
|
||||||
style: FilledButton.styleFrom(textStyle: const TextStyle(fontSize: 20)),
|
UI.button(
|
||||||
|
big: false,
|
||||||
onPressed: () => Toaster.info("Hello World", "This was a triumph!"),
|
onPressed: () => Toaster.info("Hello World", "This was a triumph!"),
|
||||||
child: const Text('Show Info Notification'),
|
text: 'Show Info Notification',
|
||||||
),
|
),
|
||||||
FilledButton(
|
SizedBox(height: 4),
|
||||||
style: FilledButton.styleFrom(textStyle: const TextStyle(fontSize: 20)),
|
UI.button(
|
||||||
|
big: false,
|
||||||
onPressed: () => Toaster.warn("Hello World", "This was a triumph!"),
|
onPressed: () => Toaster.warn("Hello World", "This was a triumph!"),
|
||||||
child: const Text('Show Warn Notification'),
|
text: 'Show Warn Notification',
|
||||||
),
|
),
|
||||||
FilledButton(
|
SizedBox(height: 4),
|
||||||
style: FilledButton.styleFrom(textStyle: const TextStyle(fontSize: 20)),
|
UI.button(
|
||||||
|
big: false,
|
||||||
onPressed: () => Toaster.error("Hello World", "This was a triumph!"),
|
onPressed: () => Toaster.error("Hello World", "This was a triumph!"),
|
||||||
child: const Text('Show Info Notification'),
|
text: 'Show Info Notification',
|
||||||
),
|
),
|
||||||
FilledButton(
|
SizedBox(height: 4),
|
||||||
style: FilledButton.styleFrom(textStyle: const TextStyle(fontSize: 20)),
|
UI.button(
|
||||||
|
big: false,
|
||||||
onPressed: () => Toaster.simple("Hello World"),
|
onPressed: () => Toaster.simple("Hello World"),
|
||||||
child: const Text('Show Simple Notification'),
|
text: 'Show Simple Notification',
|
||||||
),
|
),
|
||||||
SizedBox(height: 20),
|
SizedBox(height: 20),
|
||||||
FilledButton(
|
UI.button(
|
||||||
style: FilledButton.styleFrom(textStyle: const TextStyle(fontSize: 20)),
|
big: false,
|
||||||
onPressed: _sendTokenToServer,
|
onPressed: _sendTokenToServer,
|
||||||
child: const Text('Send FCM Token to Server'),
|
text: 'Send FCM Token to Server',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -4,6 +4,7 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
|||||||
import 'package:simplecloudnotifier/components/layout/scaffold.dart';
|
import 'package:simplecloudnotifier/components/layout/scaffold.dart';
|
||||||
import 'package:simplecloudnotifier/state/request_log.dart';
|
import 'package:simplecloudnotifier/state/request_log.dart';
|
||||||
import 'package:simplecloudnotifier/utils/toaster.dart';
|
import 'package:simplecloudnotifier/utils/toaster.dart';
|
||||||
|
import 'package:simplecloudnotifier/utils/ui.dart';
|
||||||
|
|
||||||
class DebugRequestViewPage extends StatelessWidget {
|
class DebugRequestViewPage extends StatelessWidget {
|
||||||
final SCNRequest request;
|
final SCNRequest request;
|
||||||
@ -55,17 +56,13 @@ class DebugRequestViewPage extends StatelessWidget {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: Text(title, style: TextStyle(fontWeight: FontWeight.bold)),
|
child: Text(title, style: TextStyle(fontWeight: FontWeight.bold)),
|
||||||
),
|
),
|
||||||
IconButton(
|
UI.buttonIconOnly(
|
||||||
icon: FaIcon(
|
|
||||||
FontAwesomeIcons.copy,
|
|
||||||
),
|
|
||||||
iconSize: 14,
|
iconSize: 14,
|
||||||
padding: EdgeInsets.fromLTRB(0, 0, 4, 0),
|
|
||||||
constraints: BoxConstraints(),
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Clipboard.setData(new ClipboardData(text: value));
|
Clipboard.setData(new ClipboardData(text: title));
|
||||||
Toaster.info("Clipboard", 'Copied text to Clipboard');
|
Toaster.info("Clipboard", 'Copied text to Clipboard');
|
||||||
},
|
},
|
||||||
|
icon: FontAwesomeIcons.copy,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -12,6 +12,9 @@ import 'package:simplecloudnotifier/pages/message_list/message_list_item.dart';
|
|||||||
class MessageListPage extends StatefulWidget {
|
class MessageListPage extends StatefulWidget {
|
||||||
const MessageListPage({super.key});
|
const MessageListPage({super.key});
|
||||||
|
|
||||||
|
//TODO reload on switch to tab
|
||||||
|
//TODO reload on app to foreground
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<MessageListPage> createState() => _MessageListPageState();
|
State<MessageListPage> createState() => _MessageListPageState();
|
||||||
}
|
}
|
||||||
@ -25,6 +28,7 @@ class _MessageListPageState extends State<MessageListPage> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
//TODO init with state from cache - also allow tho show cache on error
|
||||||
_pagingController.addPageRequestListener((pageKey) {
|
_pagingController.addPageRequestListener((pageKey) {
|
||||||
_fetchPage(pageKey);
|
_fetchPage(pageKey);
|
||||||
});
|
});
|
||||||
|
@ -5,6 +5,7 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
|||||||
import 'package:simplecloudnotifier/models/channel.dart';
|
import 'package:simplecloudnotifier/models/channel.dart';
|
||||||
import 'package:simplecloudnotifier/models/message.dart';
|
import 'package:simplecloudnotifier/models/message.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:simplecloudnotifier/utils/ui.dart';
|
||||||
|
|
||||||
class MessageListItem extends StatelessWidget {
|
class MessageListItem extends StatelessWidget {
|
||||||
static final _dateFormat = DateFormat('yyyy-MM-dd kk:mm');
|
static final _dateFormat = DateFormat('yyyy-MM-dd kk:mm');
|
||||||
@ -102,19 +103,10 @@ class MessageListItem extends StatelessWidget {
|
|||||||
if (message.priority == 2) SizedBox(width: 4),
|
if (message.priority == 2) SizedBox(width: 4),
|
||||||
if (message.priority == 0) FaIcon(FontAwesomeIcons.solidDown, size: 16, color: Colors.lightBlue[900]),
|
if (message.priority == 0) FaIcon(FontAwesomeIcons.solidDown, size: 16, color: Colors.lightBlue[900]),
|
||||||
if (message.priority == 0) SizedBox(width: 4),
|
if (message.priority == 0) SizedBox(width: 4),
|
||||||
Container(
|
UI.channelChip(
|
||||||
padding: const EdgeInsets.fromLTRB(4, 0, 4, 0),
|
context: context,
|
||||||
margin: const EdgeInsets.fromLTRB(0, 0, 4, 0),
|
text: resolveChannelName(message),
|
||||||
decoration: BoxDecoration(
|
margin: EdgeInsets.fromLTRB(0, 0, 4, 0),
|
||||||
color: Theme.of(context).hintColor,
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(4)),
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
resolveChannelName(message),
|
|
||||||
style: TextStyle(fontWeight: FontWeight.bold, color: Theme.of(context).cardColor, fontSize: 12),
|
|
||||||
overflow: TextOverflow.clip,
|
|
||||||
maxLines: 1,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
Expanded(child: SizedBox()),
|
Expanded(child: SizedBox()),
|
||||||
Text(
|
Text(
|
||||||
|
@ -1,9 +1,19 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:share_plus/share_plus.dart';
|
||||||
import 'package:simplecloudnotifier/api/api_client.dart';
|
import 'package:simplecloudnotifier/api/api_client.dart';
|
||||||
|
import 'package:simplecloudnotifier/api/api_exception.dart';
|
||||||
import 'package:simplecloudnotifier/components/layout/scaffold.dart';
|
import 'package:simplecloudnotifier/components/layout/scaffold.dart';
|
||||||
|
import 'package:simplecloudnotifier/models/api_error.dart';
|
||||||
|
import 'package:simplecloudnotifier/models/channel.dart';
|
||||||
|
import 'package:simplecloudnotifier/models/keytoken.dart';
|
||||||
import 'package:simplecloudnotifier/models/message.dart';
|
import 'package:simplecloudnotifier/models/message.dart';
|
||||||
import 'package:simplecloudnotifier/state/app_auth.dart';
|
import 'package:simplecloudnotifier/state/app_auth.dart';
|
||||||
|
import 'package:simplecloudnotifier/utils/toaster.dart';
|
||||||
|
import 'package:simplecloudnotifier/utils/ui.dart';
|
||||||
|
|
||||||
class MessageViewPage extends StatefulWidget {
|
class MessageViewPage extends StatefulWidget {
|
||||||
const MessageViewPage({super.key, required this.message});
|
const MessageViewPage({super.key, required this.message});
|
||||||
@ -15,18 +25,54 @@ class MessageViewPage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _MessageViewPageState extends State<MessageViewPage> {
|
class _MessageViewPageState extends State<MessageViewPage> {
|
||||||
late Future<Message>? futureMessage;
|
late Future<(Message, ChannelWithSubscription?, KeyToken?)>? mainFuture;
|
||||||
|
(Message, ChannelWithSubscription?, KeyToken?)? mainFutureSnapshot = null;
|
||||||
|
static final _dateFormat = DateFormat('yyyy-MM-dd kk:mm');
|
||||||
|
|
||||||
|
bool _monospaceMode = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
futureMessage = fetchMessage();
|
mainFuture = fetchData();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Message> fetchMessage() async {
|
Future<(Message, ChannelWithSubscription?, KeyToken?)> fetchData() async {
|
||||||
final acc = Provider.of<AppAuth>(context, listen: false);
|
final acc = Provider.of<AppAuth>(context, listen: false);
|
||||||
|
|
||||||
return await APIClient.getMessage(acc, widget.message.messageID);
|
final msg = await APIClient.getMessage(acc, widget.message.messageID);
|
||||||
|
|
||||||
|
ChannelWithSubscription? chn = null;
|
||||||
|
try {
|
||||||
|
chn = await APIClient.getChannel(acc, msg.channelID); //TODO getShortChannel (?) -> no perm
|
||||||
|
} on APIException catch (e) {
|
||||||
|
if (e.error == APIError.USER_AUTH_FAILED) {
|
||||||
|
chn = null;
|
||||||
|
} else {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyToken? tok = null;
|
||||||
|
try {
|
||||||
|
tok = await APIClient.getKeyToken(acc, msg.usedKeyID); //TODO getShortKeyToken (?) -> no perm
|
||||||
|
} on APIException catch (e) {
|
||||||
|
if (e.error == APIError.USER_AUTH_FAILED) {
|
||||||
|
tok = null;
|
||||||
|
} else {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO getShortUser for sender
|
||||||
|
|
||||||
|
//await Future.delayed(const Duration(seconds: 2), () {});
|
||||||
|
|
||||||
|
final r = (msg, chn, tok);
|
||||||
|
|
||||||
|
mainFutureSnapshot = r;
|
||||||
|
|
||||||
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -39,15 +85,18 @@ class _MessageViewPageState extends State<MessageViewPage> {
|
|||||||
return SCNScaffold(
|
return SCNScaffold(
|
||||||
title: 'Message',
|
title: 'Message',
|
||||||
showSearch: false,
|
showSearch: false,
|
||||||
child: FutureBuilder<Message>(
|
showShare: true,
|
||||||
future: futureMessage,
|
onShare: _share,
|
||||||
|
child: FutureBuilder<(Message, ChannelWithSubscription?, KeyToken?)>(
|
||||||
|
future: mainFuture,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.hasData) {
|
if (snapshot.hasData) {
|
||||||
return buildMessageView(snapshot.data!, false);
|
final (msg, chn, tok) = snapshot.data!;
|
||||||
|
return _buildMessageView(context, msg, chn, tok, false);
|
||||||
} else if (snapshot.hasError) {
|
} else if (snapshot.hasError) {
|
||||||
return Center(child: Text('${snapshot.error}')); //TODO nice error page
|
return Center(child: Text('${snapshot.error}')); //TODO nice error page
|
||||||
} else if (!widget.message.trimmed) {
|
} else if (!widget.message.trimmed) {
|
||||||
return buildMessageView(widget.message, true);
|
return _buildMessageView(context, widget.message, null, null, true);
|
||||||
} else {
|
} else {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
}
|
}
|
||||||
@ -56,15 +105,172 @@ class _MessageViewPageState extends State<MessageViewPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildMessageView(Message message, bool loading) {
|
void _share() async {
|
||||||
//TODO loading true/false indicator
|
var msg = widget.message;
|
||||||
return Center(
|
if (mainFutureSnapshot != null) {
|
||||||
|
(msg, _, _) = mainFutureSnapshot!;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.content != null) {
|
||||||
|
final result = await Share.share(msg.content!, subject: msg.title);
|
||||||
|
|
||||||
|
if (result.status == ShareResultStatus.unavailable) {
|
||||||
|
Toaster.error('Error', "Failed to open share dialog");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
final result = await Share.share(msg.title);
|
||||||
|
|
||||||
|
if (result.status == ShareResultStatus.unavailable) {
|
||||||
|
Toaster.error('Error', "Failed to open share dialog");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildMessageView(BuildContext context, Message message, ChannelWithSubscription? channel, KeyToken? token, bool loading) {
|
||||||
|
final userAccUserID = context.select<AppAuth, String?>((v) => v.userID);
|
||||||
|
|
||||||
|
return SingleChildScrollView(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(24, 16, 24, 16),
|
||||||
child: Column(
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
Text(message.title),
|
..._buildMessageHeader(context, message, channel, token, loading),
|
||||||
Text(message.content ?? ''),
|
SizedBox(height: 8),
|
||||||
|
if (message.content != null) ..._buildMessageContent(context, message, channel, token),
|
||||||
|
SizedBox(height: 8),
|
||||||
|
if (message.senderName != null) _buildMetaCard(context, FontAwesomeIcons.solidSignature, 'Sender', [message.senderName!], () => {/*TODO*/}),
|
||||||
|
_buildMetaCard(context, FontAwesomeIcons.solidGearCode, 'KeyToken', [message.usedKeyID, if (token != null) token.name], () => {/*TODO*/}),
|
||||||
|
_buildMetaCard(context, FontAwesomeIcons.solidIdCardClip, 'MessageID', [message.messageID, if (message.userMessageID != null) message.userMessageID!], null),
|
||||||
|
_buildMetaCard(context, FontAwesomeIcons.solidSnake, 'Channel', [message.channelID, channel?.channel.displayName ?? message.channelInternalName], () => {/*TODO*/}),
|
||||||
|
_buildMetaCard(context, FontAwesomeIcons.solidTimer, 'Timestamp', [message.timestamp], null),
|
||||||
|
_buildMetaCard(context, FontAwesomeIcons.solidUser, 'User', ['TODO'], () => {/*TODO*/}), //TODO
|
||||||
|
if (message.senderUserID == userAccUserID) UI.button(text: "Delete Message", onPressed: () {/*TODO*/}, color: Colors.red[900]),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _resolveChannelName(ChannelWithSubscription? channel, Message message) {
|
||||||
|
return channel?.channel.displayName ?? message.channelInternalName;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> _buildMessageHeader(BuildContext context, Message message, ChannelWithSubscription? channel, KeyToken? token, bool loading) {
|
||||||
|
return [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
UI.channelChip(
|
||||||
|
context: context,
|
||||||
|
text: _resolveChannelName(channel, message),
|
||||||
|
margin: const EdgeInsets.fromLTRB(0, 0, 4, 0),
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
Expanded(child: SizedBox()),
|
||||||
|
Text(_dateFormat.format(DateTime.parse(message.timestamp)), style: const TextStyle(fontSize: 14)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(height: 8),
|
||||||
|
if (!loading) Text(message.title, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
|
||||||
|
if (loading)
|
||||||
|
Stack(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Flexible(child: Text(message.title, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold))),
|
||||||
|
SizedBox(height: 20, width: 20),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(child: SizedBox(width: 0)),
|
||||||
|
SizedBox(child: CircularProgressIndicator(), height: 20, width: 20),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> _buildMessageContent(BuildContext context, Message message, ChannelWithSubscription? channel, KeyToken? token) {
|
||||||
|
return [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
if (message.priority == 2) FaIcon(FontAwesomeIcons.solidTriangleExclamation, size: 16, color: Colors.red[900]),
|
||||||
|
if (message.priority == 0) FaIcon(FontAwesomeIcons.solidDown, size: 16, color: Colors.lightBlue[900]),
|
||||||
|
Expanded(child: SizedBox()),
|
||||||
|
UI.buttonIconOnly(
|
||||||
|
onPressed: () {
|
||||||
|
Clipboard.setData(new ClipboardData(text: message.content ?? ''));
|
||||||
|
Toaster.info("Clipboard", 'Copied text to Clipboard');
|
||||||
|
},
|
||||||
|
icon: FontAwesomeIcons.copy,
|
||||||
|
),
|
||||||
|
UI.buttonIconOnly(
|
||||||
|
icon: _monospaceMode ? FontAwesomeIcons.lineColumns : FontAwesomeIcons.alignLeft,
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
_monospaceMode = !_monospaceMode;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
_monospaceMode
|
||||||
|
? UI.box(
|
||||||
|
context: context,
|
||||||
|
padding: const EdgeInsets.all(4),
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
child: Text(
|
||||||
|
message.content ?? '',
|
||||||
|
style: TextStyle(fontFamily: "monospace", fontFamilyFallback: <String>["Courier"]),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
borderColor: (message.priority == 2) ? Colors.red[900] : null,
|
||||||
|
)
|
||||||
|
: UI.box(
|
||||||
|
context: context,
|
||||||
|
padding: const EdgeInsets.all(4),
|
||||||
|
child: Text(message.content ?? ''),
|
||||||
|
borderColor: (message.priority == 2) ? Colors.red[900] : null,
|
||||||
|
)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildMetaCard(BuildContext context, IconData icn, String title, List<String> values, void Function()? action) {
|
||||||
|
final container = UI.box(
|
||||||
|
context: context,
|
||||||
|
padding: EdgeInsets.fromLTRB(16, 2, 4, 2),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
FaIcon(icn, size: 18),
|
||||||
|
SizedBox(width: 16),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(title, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
|
||||||
|
for (final val in values) Text(val, style: const TextStyle(fontSize: 14)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (action == null) {
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 4, horizontal: 0),
|
||||||
|
child: container,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 4, horizontal: 0),
|
||||||
|
child: InkWell(
|
||||||
|
splashColor: Theme.of(context).splashColor,
|
||||||
|
onTap: action,
|
||||||
|
child: container,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
|
||||||
import 'package:simplecloudnotifier/api/api_client.dart';
|
import 'package:simplecloudnotifier/api/api_client.dart';
|
||||||
import 'package:simplecloudnotifier/api/api_exception.dart';
|
import 'package:simplecloudnotifier/api/api_exception.dart';
|
||||||
import 'package:simplecloudnotifier/models/client.dart';
|
import 'package:simplecloudnotifier/models/client.dart';
|
||||||
@ -63,6 +62,8 @@ class AppAuth extends ChangeNotifier implements TokenSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void load() {
|
void load() {
|
||||||
|
//final cdat = Globals().sharedPrefs.getString('auth.cdate');
|
||||||
|
//final mdat = Globals().sharedPrefs.getString('auth.mdate');
|
||||||
final uid = Globals().sharedPrefs.getString('auth.userid');
|
final uid = Globals().sharedPrefs.getString('auth.userid');
|
||||||
final cid = Globals().sharedPrefs.getString('auth.clientid');
|
final cid = Globals().sharedPrefs.getString('auth.clientid');
|
||||||
final toka = Globals().sharedPrefs.getString('auth.tokenadmin');
|
final toka = Globals().sharedPrefs.getString('auth.tokenadmin');
|
||||||
@ -85,17 +86,23 @@ class AppAuth extends ChangeNotifier implements TokenSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> save() async {
|
Future<void> save() async {
|
||||||
final prefs = await SharedPreferences.getInstance();
|
|
||||||
if (_clientID == null || _userID == null || _tokenAdmin == null || _tokenSend == null) {
|
if (_clientID == null || _userID == null || _tokenAdmin == null || _tokenSend == null) {
|
||||||
await prefs.remove('auth.userid');
|
await Globals().sharedPrefs.remove('auth.userid');
|
||||||
await prefs.remove('auth.tokenadmin');
|
await Globals().sharedPrefs.remove('auth.clientid');
|
||||||
await prefs.remove('auth.tokensend');
|
await Globals().sharedPrefs.remove('auth.tokenadmin');
|
||||||
|
await Globals().sharedPrefs.remove('auth.tokensend');
|
||||||
|
await Globals().sharedPrefs.setString('auth.cdate', "");
|
||||||
|
await Globals().sharedPrefs.setString('auth.mdate', DateTime.now().toIso8601String());
|
||||||
} else {
|
} else {
|
||||||
await prefs.setString('auth.userid', _userID!);
|
await Globals().sharedPrefs.setString('auth.userid', _userID!);
|
||||||
await prefs.setString('auth.clientid', _clientID!);
|
await Globals().sharedPrefs.setString('auth.clientid', _clientID!);
|
||||||
await prefs.setString('auth.tokenadmin', _tokenAdmin!);
|
await Globals().sharedPrefs.setString('auth.tokenadmin', _tokenAdmin!);
|
||||||
await prefs.setString('auth.tokensend', _tokenSend!);
|
await Globals().sharedPrefs.setString('auth.tokensend', _tokenSend!);
|
||||||
|
if (Globals().sharedPrefs.getString('auth.cdate') == null) await Globals().sharedPrefs.setString('auth.cdate', DateTime.now().toIso8601String());
|
||||||
|
await Globals().sharedPrefs.setString('auth.mdate', DateTime.now().toIso8601String());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Globals().sharedPrefs.setString('auth.mdate', DateTime.now().toIso8601String());
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<User> loadUser({bool force = false}) async {
|
Future<User> loadUser({bool force = false}) async {
|
||||||
|
1
flutter/lib/utils/navi.dart
Normal file
1
flutter/lib/utils/navi.dart
Normal file
@ -0,0 +1 @@
|
|||||||
|
class Navi {}
|
109
flutter/lib/utils/ui.dart
Normal file
109
flutter/lib/utils/ui.dart
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
|
|
||||||
|
class UI {
|
||||||
|
static const double DefaultBorderRadius = 4;
|
||||||
|
|
||||||
|
static Widget button({required String text, required void Function() onPressed, bool big = false, Color? color = null, bool tonal = false, IconData? icon = null}) {
|
||||||
|
final double fontSize = big ? 24 : 14;
|
||||||
|
final padding = big ? EdgeInsets.fromLTRB(8, 12, 8, 12) : null;
|
||||||
|
|
||||||
|
final style = FilledButton.styleFrom(
|
||||||
|
textStyle: TextStyle(fontSize: fontSize),
|
||||||
|
padding: padding,
|
||||||
|
backgroundColor: color,
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(DefaultBorderRadius)),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (tonal) {
|
||||||
|
if (icon != null) {
|
||||||
|
return FilledButton.tonalIcon(
|
||||||
|
style: style,
|
||||||
|
onPressed: onPressed,
|
||||||
|
icon: Icon(icon),
|
||||||
|
label: Text(text),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return FilledButton.tonal(
|
||||||
|
style: style,
|
||||||
|
onPressed: onPressed,
|
||||||
|
child: Text(text),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (icon != null) {
|
||||||
|
return FilledButton.icon(
|
||||||
|
style: style,
|
||||||
|
onPressed: onPressed,
|
||||||
|
icon: Icon(icon),
|
||||||
|
label: Text(text),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return FilledButton(
|
||||||
|
style: style,
|
||||||
|
onPressed: onPressed,
|
||||||
|
child: Text(text),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Widget buttonIconOnly({
|
||||||
|
required void Function() onPressed,
|
||||||
|
required IconData icon,
|
||||||
|
double? iconSize = null,
|
||||||
|
}) {
|
||||||
|
return IconButton(
|
||||||
|
icon: FaIcon(icon),
|
||||||
|
iconSize: iconSize ?? 18,
|
||||||
|
padding: EdgeInsets.all(4),
|
||||||
|
constraints: BoxConstraints(),
|
||||||
|
style: ButtonStyle(tapTargetSize: MaterialTapTargetSize.shrinkWrap),
|
||||||
|
onPressed: onPressed,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Widget buttonCard({required BuildContext context, required Widget child, required void Function() onTap, EdgeInsets? margin = null}) {
|
||||||
|
return Card.filled(
|
||||||
|
margin: margin,
|
||||||
|
shape: BeveledRectangleBorder(borderRadius: BorderRadius.circular(DefaultBorderRadius)),
|
||||||
|
color: Theme.of(context).cardTheme.color,
|
||||||
|
child: InkWell(
|
||||||
|
splashColor: Theme.of(context).splashColor,
|
||||||
|
onTap: onTap,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Widget channelChip({required BuildContext context, required String text, EdgeInsets? margin = null, double fontSize = 12}) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.fromLTRB(4, 0, 4, 0),
|
||||||
|
margin: margin,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).hintColor,
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(DefaultBorderRadius)),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
text,
|
||||||
|
style: TextStyle(fontWeight: FontWeight.bold, color: Theme.of(context).cardColor, fontSize: fontSize),
|
||||||
|
overflow: TextOverflow.clip,
|
||||||
|
maxLines: 1,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Widget box({required BuildContext context, required Widget child, required EdgeInsets? padding, Color? borderColor = null}) {
|
||||||
|
return Container(
|
||||||
|
padding: padding ?? EdgeInsets.all(4),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(color: borderColor ?? Theme.of(context).hintColor),
|
||||||
|
borderRadius: BorderRadius.circular(DefaultBorderRadius),
|
||||||
|
),
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -10,6 +10,7 @@ import firebase_core
|
|||||||
import firebase_messaging
|
import firebase_messaging
|
||||||
import package_info_plus
|
import package_info_plus
|
||||||
import path_provider_foundation
|
import path_provider_foundation
|
||||||
|
import share_plus
|
||||||
import shared_preferences_foundation
|
import shared_preferences_foundation
|
||||||
import url_launcher_macos
|
import url_launcher_macos
|
||||||
|
|
||||||
@ -19,6 +20,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
|||||||
FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin"))
|
FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin"))
|
||||||
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
||||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||||
|
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
|
||||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||||
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||||
}
|
}
|
||||||
|
@ -161,6 +161,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.1"
|
version: "3.1.1"
|
||||||
|
cross_file:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: cross_file
|
||||||
|
sha256: "55d7b444feb71301ef6b8838dbc1ae02e63dd48c8773f3810ff53bb1e2945b32"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.3.4+1"
|
||||||
crypto:
|
crypto:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -687,6 +695,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.1.0"
|
version: "4.1.0"
|
||||||
|
share_plus:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: share_plus
|
||||||
|
sha256: ef3489a969683c4f3d0239010cc8b7a2a46543a8d139e111c06c558875083544
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "9.0.0"
|
||||||
|
share_plus_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: share_plus_platform_interface
|
||||||
|
sha256: "0f9e4418835d1b2c3ae78fdb918251959106cefdbc4dd43526e182f80e82f6d4"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.0.0"
|
||||||
shared_preferences:
|
shared_preferences:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -941,7 +965,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.1"
|
version: "3.1.1"
|
||||||
uuid:
|
uuid:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: uuid
|
name: uuid
|
||||||
sha256: "814e9e88f21a176ae1359149021870e87f7cddaf633ab678a5d2b0bff7fd1ba8"
|
sha256: "814e9e88f21a176ae1359149021870e87f7cddaf633ab678a5d2b0bff7fd1ba8"
|
||||||
|
@ -29,6 +29,8 @@ dependencies:
|
|||||||
firebase_messaging: ^14.9.4
|
firebase_messaging: ^14.9.4
|
||||||
device_info_plus: ^10.1.0
|
device_info_plus: ^10.1.0
|
||||||
toastification: ^2.0.0
|
toastification: ^2.0.0
|
||||||
|
uuid: ^4.4.0
|
||||||
|
share_plus: ^9.0.0
|
||||||
|
|
||||||
|
|
||||||
dependency_overrides:
|
dependency_overrides:
|
||||||
|
@ -7,11 +7,14 @@
|
|||||||
#include "generated_plugin_registrant.h"
|
#include "generated_plugin_registrant.h"
|
||||||
|
|
||||||
#include <firebase_core/firebase_core_plugin_c_api.h>
|
#include <firebase_core/firebase_core_plugin_c_api.h>
|
||||||
|
#include <share_plus/share_plus_windows_plugin_c_api.h>
|
||||||
#include <url_launcher_windows/url_launcher_windows.h>
|
#include <url_launcher_windows/url_launcher_windows.h>
|
||||||
|
|
||||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||||
FirebaseCorePluginCApiRegisterWithRegistrar(
|
FirebaseCorePluginCApiRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("FirebaseCorePluginCApi"));
|
registry->GetRegistrarForPlugin("FirebaseCorePluginCApi"));
|
||||||
|
SharePlusWindowsPluginCApiRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi"));
|
||||||
UrlLauncherWindowsRegisterWithRegistrar(
|
UrlLauncherWindowsRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
firebase_core
|
firebase_core
|
||||||
|
share_plus
|
||||||
url_launcher_windows
|
url_launcher_windows
|
||||||
)
|
)
|
||||||
|
|
||||||
|
127
scnserver/api/handler/apiPreview.go
Normal file
127
scnserver/api/handler/apiPreview.go
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"blackforestbytes.com/simplecloudnotifier/api/apierr"
|
||||||
|
"blackforestbytes.com/simplecloudnotifier/api/ginresp"
|
||||||
|
"blackforestbytes.com/simplecloudnotifier/models"
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetUserPreview swaggerdoc
|
||||||
|
//
|
||||||
|
// @Summary Get a user (similar to api-user-get, but can be called from anyone and only returns a subset of fields)
|
||||||
|
// @ID api-user-get-preview
|
||||||
|
// @Tags API-v2
|
||||||
|
//
|
||||||
|
// @Param uid path string true "UserID"
|
||||||
|
//
|
||||||
|
// @Success 200 {object} models.UserPreviewJSON
|
||||||
|
// @Failure 400 {object} ginresp.apiError "supplied values/parameters cannot be parsed / are invalid"
|
||||||
|
// @Failure 401 {object} ginresp.apiError "user is not authorized / has missing permissions"
|
||||||
|
// @Failure 404 {object} ginresp.apiError "user not found"
|
||||||
|
// @Failure 500 {object} ginresp.apiError "internal server error"
|
||||||
|
//
|
||||||
|
// @Router /api/v2/preview/users/{uid} [GET]
|
||||||
|
func (h APIHandler) GetUserPreview(g *gin.Context) ginresp.HTTPResponse {
|
||||||
|
type uri struct {
|
||||||
|
UserID models.UserID `uri:"uid" binding:"entityid"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var u uri
|
||||||
|
ctx, errResp := h.app.StartRequest(g, &u, nil, nil, nil)
|
||||||
|
if errResp != nil {
|
||||||
|
return *errResp
|
||||||
|
}
|
||||||
|
defer ctx.Cancel()
|
||||||
|
|
||||||
|
user, err := h.database.GetUser(ctx, u.UserID)
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return ginresp.APIError(g, 404, apierr.USER_NOT_FOUND, "User not found", err)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to query user", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, user.JSONPreview()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetChannelPreview swaggerdoc
|
||||||
|
//
|
||||||
|
// @Summary Get a single channel (similar to api-channels-get, but can be called from anyone and only returns a subset of fields)
|
||||||
|
// @ID api-channels-get-preview
|
||||||
|
// @Tags API-v2
|
||||||
|
//
|
||||||
|
// @Param cid path string true "ChannelID"
|
||||||
|
//
|
||||||
|
// @Success 200 {object} models.ChannelPreviewJSON
|
||||||
|
// @Failure 400 {object} ginresp.apiError "supplied values/parameters cannot be parsed / are invalid"
|
||||||
|
// @Failure 401 {object} ginresp.apiError "user is not authorized / has missing permissions"
|
||||||
|
// @Failure 404 {object} ginresp.apiError "channel not found"
|
||||||
|
// @Failure 500 {object} ginresp.apiError "internal server error"
|
||||||
|
//
|
||||||
|
// @Router /api/v2/preview/channels/{cid} [GET]
|
||||||
|
func (h APIHandler) GetChannelPreview(g *gin.Context) ginresp.HTTPResponse {
|
||||||
|
type uri struct {
|
||||||
|
UserID models.UserID `uri:"uid" binding:"entityid"`
|
||||||
|
ChannelID models.ChannelID `uri:"cid" binding:"entityid"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var u uri
|
||||||
|
ctx, errResp := h.app.StartRequest(g, &u, nil, nil, nil)
|
||||||
|
if errResp != nil {
|
||||||
|
return *errResp
|
||||||
|
}
|
||||||
|
defer ctx.Cancel()
|
||||||
|
|
||||||
|
channel, err := h.database.GetChannelByID(ctx, u.ChannelID)
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return ginresp.APIError(g, 404, apierr.CHANNEL_NOT_FOUND, "Channel not found", err)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to query channel", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, channel.JSONPreview()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserKeyPreview swaggerdoc
|
||||||
|
//
|
||||||
|
// @Summary Get a single key (similar to api-tokenkeys-get, but can be called from anyone and only returns a subset of fields)
|
||||||
|
// @ID api-tokenkeys-get-preview
|
||||||
|
// @Tags API-v2
|
||||||
|
//
|
||||||
|
// @Param kid path string true "TokenKeyID"
|
||||||
|
//
|
||||||
|
// @Success 200 {object} models.KeyTokenPreviewJSON
|
||||||
|
// @Failure 400 {object} ginresp.apiError "supplied values/parameters cannot be parsed / are invalid"
|
||||||
|
// @Failure 401 {object} ginresp.apiError "user is not authorized / has missing permissions"
|
||||||
|
// @Failure 404 {object} ginresp.apiError "message not found"
|
||||||
|
// @Failure 500 {object} ginresp.apiError "internal server error"
|
||||||
|
//
|
||||||
|
// @Router /api/v2/preview/keys/{kid} [GET]
|
||||||
|
func (h APIHandler) GetUserKeyPreview(g *gin.Context) ginresp.HTTPResponse {
|
||||||
|
type uri struct {
|
||||||
|
UserID models.UserID `uri:"uid" binding:"entityid"`
|
||||||
|
KeyID models.KeyTokenID `uri:"kid" binding:"entityid"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var u uri
|
||||||
|
ctx, errResp := h.app.StartRequest(g, &u, nil, nil, nil)
|
||||||
|
if errResp != nil {
|
||||||
|
return *errResp
|
||||||
|
}
|
||||||
|
defer ctx.Cancel()
|
||||||
|
|
||||||
|
keytoken, err := h.database.GetKeyToken(ctx, u.UserID, u.KeyID)
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return ginresp.APIError(g, 404, apierr.KEY_NOT_FOUND, "Key not found", err)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to query client", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, keytoken.JSONPreview()))
|
||||||
|
}
|
@ -157,6 +157,10 @@ func (r *Router) Init(e *gin.Engine) error {
|
|||||||
apiv2.GET("/messages", r.Wrap(r.apiHandler.ListMessages))
|
apiv2.GET("/messages", r.Wrap(r.apiHandler.ListMessages))
|
||||||
apiv2.GET("/messages/:mid", r.Wrap(r.apiHandler.GetMessage))
|
apiv2.GET("/messages/:mid", r.Wrap(r.apiHandler.GetMessage))
|
||||||
apiv2.DELETE("/messages/:mid", r.Wrap(r.apiHandler.DeleteMessage))
|
apiv2.DELETE("/messages/:mid", r.Wrap(r.apiHandler.DeleteMessage))
|
||||||
|
|
||||||
|
apiv2.GET("/preview/users/:uid", r.Wrap(r.apiHandler.GetUserPreview))
|
||||||
|
apiv2.GET("/preview/keys/:kid", r.Wrap(r.apiHandler.GetUserKeyPreview))
|
||||||
|
apiv2.GET("/preview/channels/:cid", r.Wrap(r.apiHandler.GetChannelPreview))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ================ Send API (unversioned) ================
|
// ================ Send API (unversioned) ================
|
||||||
|
@ -41,6 +41,16 @@ func (c Channel) WithSubscription(sub *Subscription) ChannelWithSubscription {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c Channel) JSONPreview() ChannelPreviewJSON {
|
||||||
|
return ChannelPreviewJSON{
|
||||||
|
ChannelID: c.ChannelID,
|
||||||
|
OwnerUserID: c.OwnerUserID,
|
||||||
|
InternalName: c.InternalName,
|
||||||
|
DisplayName: c.DisplayName,
|
||||||
|
DescriptionName: c.DescriptionName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type ChannelWithSubscription struct {
|
type ChannelWithSubscription struct {
|
||||||
Channel
|
Channel
|
||||||
Subscription *Subscription
|
Subscription *Subscription
|
||||||
@ -74,6 +84,14 @@ type ChannelWithSubscriptionJSON struct {
|
|||||||
Subscription *SubscriptionJSON `json:"subscription"`
|
Subscription *SubscriptionJSON `json:"subscription"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ChannelPreviewJSON struct {
|
||||||
|
ChannelID ChannelID `json:"channel_id"`
|
||||||
|
OwnerUserID UserID `json:"owner_user_id"`
|
||||||
|
InternalName string `json:"internal_name"`
|
||||||
|
DisplayName string `json:"display_name"`
|
||||||
|
DescriptionName *string `json:"description_name"`
|
||||||
|
}
|
||||||
|
|
||||||
type ChannelDB struct {
|
type ChannelDB struct {
|
||||||
ChannelID ChannelID `db:"channel_id"`
|
ChannelID ChannelID `db:"channel_id"`
|
||||||
OwnerUserID UserID `db:"owner_user_id"`
|
OwnerUserID UserID `db:"owner_user_id"`
|
||||||
|
@ -5,7 +5,7 @@ package models
|
|||||||
import "gogs.mikescher.com/BlackForestBytes/goext/langext"
|
import "gogs.mikescher.com/BlackForestBytes/goext/langext"
|
||||||
import "gogs.mikescher.com/BlackForestBytes/goext/enums"
|
import "gogs.mikescher.com/BlackForestBytes/goext/enums"
|
||||||
|
|
||||||
const ChecksumEnumGenerator = "5b115c5f107801af608630d2c5adce57cd4b050d176c8cd3db5c132020bf153c" // GoExtVersion: 0.0.463
|
const ChecksumEnumGenerator = "e500346e3f60b3abf78558ec3df128c3be2a1cefa71c4f1feba9293d14eb85d1" // GoExtVersion: 0.0.463
|
||||||
|
|
||||||
// ================================ ClientType ================================
|
// ================================ ClientType ================================
|
||||||
//
|
//
|
||||||
|
@ -15,7 +15,7 @@ import "reflect"
|
|||||||
import "regexp"
|
import "regexp"
|
||||||
import "strings"
|
import "strings"
|
||||||
|
|
||||||
const ChecksumCharsetIDGenerator = "5b115c5f107801af608630d2c5adce57cd4b050d176c8cd3db5c132020bf153c" // GoExtVersion: 0.0.463
|
const ChecksumCharsetIDGenerator = "e500346e3f60b3abf78558ec3df128c3be2a1cefa71c4f1feba9293d14eb85d1" // GoExtVersion: 0.0.463
|
||||||
|
|
||||||
const idlen = 24
|
const idlen = 24
|
||||||
|
|
||||||
|
@ -92,6 +92,17 @@ func (k KeyToken) JSON() KeyTokenJSON {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (k KeyToken) JSONPreview() KeyTokenPreviewJSON {
|
||||||
|
return KeyTokenPreviewJSON{
|
||||||
|
KeyTokenID: k.KeyTokenID,
|
||||||
|
Name: k.Name,
|
||||||
|
OwnerUserID: k.OwnerUserID,
|
||||||
|
AllChannels: k.AllChannels,
|
||||||
|
Channels: k.Channels,
|
||||||
|
Permissions: k.Permissions.String(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type KeyTokenJSON struct {
|
type KeyTokenJSON struct {
|
||||||
KeyTokenID KeyTokenID `json:"keytoken_id"`
|
KeyTokenID KeyTokenID `json:"keytoken_id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
@ -109,6 +120,15 @@ type KeyTokenWithTokenJSON struct {
|
|||||||
Token string `json:"token"`
|
Token string `json:"token"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type KeyTokenPreviewJSON struct {
|
||||||
|
KeyTokenID KeyTokenID `json:"keytoken_id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
OwnerUserID UserID `json:"owner_user_id"`
|
||||||
|
AllChannels bool `json:"all_channels"`
|
||||||
|
Channels []ChannelID `json:"channels"`
|
||||||
|
Permissions string `json:"permissions"`
|
||||||
|
}
|
||||||
|
|
||||||
func (j KeyTokenJSON) WithToken(tok string) KeyTokenWithTokenJSON {
|
func (j KeyTokenJSON) WithToken(tok string) KeyTokenWithTokenJSON {
|
||||||
return KeyTokenWithTokenJSON{
|
return KeyTokenWithTokenJSON{
|
||||||
KeyTokenJSON: j,
|
KeyTokenJSON: j,
|
||||||
|
@ -116,6 +116,13 @@ func (u User) MaxTimestampDiffHours() int {
|
|||||||
return 24
|
return 24
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u User) JSONPreview() UserPreviewJSON {
|
||||||
|
return UserPreviewJSON{
|
||||||
|
UserID: u.UserID,
|
||||||
|
Username: u.Username,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type UserJSON struct {
|
type UserJSON struct {
|
||||||
UserID UserID `json:"user_id"`
|
UserID UserID `json:"user_id"`
|
||||||
Username *string `json:"username"`
|
Username *string `json:"username"`
|
||||||
@ -137,6 +144,11 @@ type UserJSON struct {
|
|||||||
MaxUserMessageIDLength int `json:"max_user_message_id_length"`
|
MaxUserMessageIDLength int `json:"max_user_message_id_length"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UserPreviewJSON struct {
|
||||||
|
UserID UserID `json:"user_id"`
|
||||||
|
Username *string `json:"username"`
|
||||||
|
}
|
||||||
|
|
||||||
type UserJSONWithClientsAndKeys struct {
|
type UserJSONWithClientsAndKeys struct {
|
||||||
UserJSON
|
UserJSON
|
||||||
Clients []ClientJSON `json:"clients"`
|
Clients []ClientJSON `json:"clients"`
|
||||||
|
@ -19,37 +19,61 @@
|
|||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"example": "test",
|
||||||
|
"name": "channel",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"example": "This is a message",
|
||||||
"name": "content",
|
"name": "content",
|
||||||
"in": "query"
|
"in": "query"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"example": "P3TNH8mvv14fm",
|
||||||
|
"name": "key",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"example": "db8b0e6a-a08c-4646",
|
||||||
"name": "msg_id",
|
"name": "msg_id",
|
||||||
"in": "query"
|
"in": "query"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"enum": [
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
2
|
||||||
|
],
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
|
"example": 1,
|
||||||
"name": "priority",
|
"name": "priority",
|
||||||
"in": "query"
|
"in": "query"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"example": "example-server",
|
||||||
|
"name": "sender_name",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "number",
|
"type": "number",
|
||||||
|
"example": 1669824037,
|
||||||
"name": "timestamp",
|
"name": "timestamp",
|
||||||
"in": "query"
|
"in": "query"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"example": "Hello World",
|
||||||
"name": "title",
|
"name": "title",
|
||||||
"in": "query"
|
"in": "query"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"type": "integer",
|
|
||||||
"name": "user_id",
|
|
||||||
"in": "query"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"name": "user_key",
|
"example": "7725",
|
||||||
|
"name": "user_id",
|
||||||
"in": "query"
|
"in": "query"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -62,37 +86,61 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"example": "test",
|
||||||
|
"name": "channel",
|
||||||
|
"in": "formData"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"example": "This is a message",
|
||||||
"name": "content",
|
"name": "content",
|
||||||
"in": "formData"
|
"in": "formData"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"example": "P3TNH8mvv14fm",
|
||||||
|
"name": "key",
|
||||||
|
"in": "formData"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"example": "db8b0e6a-a08c-4646",
|
||||||
"name": "msg_id",
|
"name": "msg_id",
|
||||||
"in": "formData"
|
"in": "formData"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"enum": [
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
2
|
||||||
|
],
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
|
"example": 1,
|
||||||
"name": "priority",
|
"name": "priority",
|
||||||
"in": "formData"
|
"in": "formData"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"example": "example-server",
|
||||||
|
"name": "sender_name",
|
||||||
|
"in": "formData"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "number",
|
"type": "number",
|
||||||
|
"example": 1669824037,
|
||||||
"name": "timestamp",
|
"name": "timestamp",
|
||||||
"in": "formData"
|
"in": "formData"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"example": "Hello World",
|
||||||
"name": "title",
|
"name": "title",
|
||||||
"in": "formData"
|
"in": "formData"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"type": "integer",
|
|
||||||
"name": "user_id",
|
|
||||||
"in": "formData"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"name": "user_key",
|
"example": "7725",
|
||||||
|
"name": "user_id",
|
||||||
"in": "formData"
|
"in": "formData"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -1009,6 +1057,156 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/v2/preview/channels/{cid}": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"API-v2"
|
||||||
|
],
|
||||||
|
"summary": "Get a single channel (similar to api-channels-get, but can be called from anyone and only returns a subset of fields)",
|
||||||
|
"operationId": "api-channels-get-preview",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "ChannelID",
|
||||||
|
"name": "cid",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/models.ChannelPreviewJSON"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "supplied values/parameters cannot be parsed / are invalid",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/ginresp.apiError"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "user is not authorized / has missing permissions",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/ginresp.apiError"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "channel not found",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/ginresp.apiError"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "internal server error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/ginresp.apiError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v2/preview/keys/{kid}": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"API-v2"
|
||||||
|
],
|
||||||
|
"summary": "Get a single key (similar to api-tokenkeys-get, but can be called from anyone and only returns a subset of fields)",
|
||||||
|
"operationId": "api-tokenkeys-get-preview",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "TokenKeyID",
|
||||||
|
"name": "kid",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/models.KeyTokenPreviewJSON"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "supplied values/parameters cannot be parsed / are invalid",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/ginresp.apiError"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "user is not authorized / has missing permissions",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/ginresp.apiError"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "message not found",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/ginresp.apiError"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "internal server error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/ginresp.apiError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v2/preview/users/{uid}": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"API-v2"
|
||||||
|
],
|
||||||
|
"summary": "Get a user (similar to api-user-get, but can be called from anyone and only returns a subset of fields)",
|
||||||
|
"operationId": "api-user-get-preview",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "UserID",
|
||||||
|
"name": "uid",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/models.UserPreviewJSON"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "supplied values/parameters cannot be parsed / are invalid",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/ginresp.apiError"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "user is not authorized / has missing permissions",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/ginresp.apiError"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "user not found",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/ginresp.apiError"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "internal server error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/ginresp.apiError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/v2/users": {
|
"/api/v2/users": {
|
||||||
"post": {
|
"post": {
|
||||||
"tags": [
|
"tags": [
|
||||||
@ -2567,37 +2765,61 @@
|
|||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"example": "test",
|
||||||
|
"name": "channel",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"example": "This is a message",
|
||||||
"name": "content",
|
"name": "content",
|
||||||
"in": "query"
|
"in": "query"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"example": "P3TNH8mvv14fm",
|
||||||
|
"name": "key",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"example": "db8b0e6a-a08c-4646",
|
||||||
"name": "msg_id",
|
"name": "msg_id",
|
||||||
"in": "query"
|
"in": "query"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"enum": [
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
2
|
||||||
|
],
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
|
"example": 1,
|
||||||
"name": "priority",
|
"name": "priority",
|
||||||
"in": "query"
|
"in": "query"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"example": "example-server",
|
||||||
|
"name": "sender_name",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "number",
|
"type": "number",
|
||||||
|
"example": 1669824037,
|
||||||
"name": "timestamp",
|
"name": "timestamp",
|
||||||
"in": "query"
|
"in": "query"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"example": "Hello World",
|
||||||
"name": "title",
|
"name": "title",
|
||||||
"in": "query"
|
"in": "query"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"type": "integer",
|
|
||||||
"name": "user_id",
|
|
||||||
"in": "query"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"name": "user_key",
|
"example": "7725",
|
||||||
|
"name": "user_id",
|
||||||
"in": "query"
|
"in": "query"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -2610,37 +2832,61 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"example": "test",
|
||||||
|
"name": "channel",
|
||||||
|
"in": "formData"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"example": "This is a message",
|
||||||
"name": "content",
|
"name": "content",
|
||||||
"in": "formData"
|
"in": "formData"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"example": "P3TNH8mvv14fm",
|
||||||
|
"name": "key",
|
||||||
|
"in": "formData"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"example": "db8b0e6a-a08c-4646",
|
||||||
"name": "msg_id",
|
"name": "msg_id",
|
||||||
"in": "formData"
|
"in": "formData"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"enum": [
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
2
|
||||||
|
],
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
|
"example": 1,
|
||||||
"name": "priority",
|
"name": "priority",
|
||||||
"in": "formData"
|
"in": "formData"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"example": "example-server",
|
||||||
|
"name": "sender_name",
|
||||||
|
"in": "formData"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "number",
|
"type": "number",
|
||||||
|
"example": 1669824037,
|
||||||
"name": "timestamp",
|
"name": "timestamp",
|
||||||
"in": "formData"
|
"in": "formData"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"example": "Hello World",
|
||||||
"name": "title",
|
"name": "title",
|
||||||
"in": "formData"
|
"in": "formData"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"type": "integer",
|
|
||||||
"name": "user_id",
|
|
||||||
"in": "formData"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"name": "user_key",
|
"example": "7725",
|
||||||
|
"name": "user_id",
|
||||||
"in": "formData"
|
"in": "formData"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -2689,72 +2935,120 @@
|
|||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"example": "test",
|
||||||
|
"name": "channel",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"example": "This is a message",
|
||||||
"name": "content",
|
"name": "content",
|
||||||
"in": "query"
|
"in": "query"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"example": "P3TNH8mvv14fm",
|
||||||
|
"name": "key",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"example": "db8b0e6a-a08c-4646",
|
||||||
"name": "msg_id",
|
"name": "msg_id",
|
||||||
"in": "query"
|
"in": "query"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"enum": [
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
2
|
||||||
|
],
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
|
"example": 1,
|
||||||
"name": "priority",
|
"name": "priority",
|
||||||
"in": "query"
|
"in": "query"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"example": "example-server",
|
||||||
|
"name": "sender_name",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "number",
|
"type": "number",
|
||||||
|
"example": 1669824037,
|
||||||
"name": "timestamp",
|
"name": "timestamp",
|
||||||
"in": "query"
|
"in": "query"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"example": "Hello World",
|
||||||
"name": "title",
|
"name": "title",
|
||||||
"in": "query"
|
"in": "query"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "integer",
|
"type": "string",
|
||||||
|
"example": "7725",
|
||||||
"name": "user_id",
|
"name": "user_id",
|
||||||
"in": "query"
|
"in": "query"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"name": "user_key",
|
"example": "test",
|
||||||
"in": "query"
|
"name": "channel",
|
||||||
|
"in": "formData"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"example": "This is a message",
|
||||||
"name": "content",
|
"name": "content",
|
||||||
"in": "formData"
|
"in": "formData"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"example": "P3TNH8mvv14fm",
|
||||||
|
"name": "key",
|
||||||
|
"in": "formData"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"example": "db8b0e6a-a08c-4646",
|
||||||
"name": "msg_id",
|
"name": "msg_id",
|
||||||
"in": "formData"
|
"in": "formData"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"enum": [
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
2
|
||||||
|
],
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
|
"example": 1,
|
||||||
"name": "priority",
|
"name": "priority",
|
||||||
"in": "formData"
|
"in": "formData"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"example": "example-server",
|
||||||
|
"name": "sender_name",
|
||||||
|
"in": "formData"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "number",
|
"type": "number",
|
||||||
|
"example": 1669824037,
|
||||||
"name": "timestamp",
|
"name": "timestamp",
|
||||||
"in": "formData"
|
"in": "formData"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"example": "Hello World",
|
||||||
"name": "title",
|
"name": "title",
|
||||||
"in": "formData"
|
"in": "formData"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"type": "integer",
|
|
||||||
"name": "user_id",
|
|
||||||
"in": "formData"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"name": "user_key",
|
"example": "7725",
|
||||||
|
"name": "user_id",
|
||||||
"in": "formData"
|
"in": "formData"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -3251,26 +3545,46 @@
|
|||||||
"handler.SendMessage.combined": {
|
"handler.SendMessage.combined": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"channel": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "test"
|
||||||
|
},
|
||||||
"content": {
|
"content": {
|
||||||
"type": "string"
|
"type": "string",
|
||||||
|
"example": "This is a message"
|
||||||
|
},
|
||||||
|
"key": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "P3TNH8mvv14fm"
|
||||||
},
|
},
|
||||||
"msg_id": {
|
"msg_id": {
|
||||||
"type": "string"
|
"type": "string",
|
||||||
|
"example": "db8b0e6a-a08c-4646"
|
||||||
},
|
},
|
||||||
"priority": {
|
"priority": {
|
||||||
"type": "integer"
|
"type": "integer",
|
||||||
|
"enum": [
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
2
|
||||||
|
],
|
||||||
|
"example": 1
|
||||||
|
},
|
||||||
|
"sender_name": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "example-server"
|
||||||
},
|
},
|
||||||
"timestamp": {
|
"timestamp": {
|
||||||
"type": "number"
|
"type": "number",
|
||||||
|
"example": 1669824037
|
||||||
},
|
},
|
||||||
"title": {
|
"title": {
|
||||||
"type": "string"
|
"type": "string",
|
||||||
|
"example": "Hello World"
|
||||||
},
|
},
|
||||||
"user_id": {
|
"user_id": {
|
||||||
"type": "integer"
|
"type": "string",
|
||||||
},
|
"example": "7725"
|
||||||
"user_key": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -3299,7 +3613,7 @@
|
|||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"scn_msg_id": {
|
"scn_msg_id": {
|
||||||
"type": "integer"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"success": {
|
"success": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
@ -3487,6 +3801,26 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"models.ChannelPreviewJSON": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"channel_id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description_name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"display_name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"internal_name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"owner_user_id": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"models.ChannelWithSubscriptionJSON": {
|
"models.ChannelWithSubscriptionJSON": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@ -3630,6 +3964,32 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"models.KeyTokenPreviewJSON": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"all_channels": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"channels": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"keytoken_id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"owner_user_id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"permissions": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"models.KeyTokenWithTokenJSON": {
|
"models.KeyTokenWithTokenJSON": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@ -3870,6 +4230,17 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"models.UserPreviewJSON": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"user_id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tags": [
|
"tags": [
|
||||||
|
@ -327,19 +327,36 @@ definitions:
|
|||||||
type: object
|
type: object
|
||||||
handler.SendMessage.combined:
|
handler.SendMessage.combined:
|
||||||
properties:
|
properties:
|
||||||
|
channel:
|
||||||
|
example: test
|
||||||
|
type: string
|
||||||
content:
|
content:
|
||||||
|
example: This is a message
|
||||||
|
type: string
|
||||||
|
key:
|
||||||
|
example: P3TNH8mvv14fm
|
||||||
type: string
|
type: string
|
||||||
msg_id:
|
msg_id:
|
||||||
|
example: db8b0e6a-a08c-4646
|
||||||
type: string
|
type: string
|
||||||
priority:
|
priority:
|
||||||
|
enum:
|
||||||
|
- 0
|
||||||
|
- 1
|
||||||
|
- 2
|
||||||
|
example: 1
|
||||||
type: integer
|
type: integer
|
||||||
|
sender_name:
|
||||||
|
example: example-server
|
||||||
|
type: string
|
||||||
timestamp:
|
timestamp:
|
||||||
|
example: 1669824037
|
||||||
type: number
|
type: number
|
||||||
title:
|
title:
|
||||||
|
example: Hello World
|
||||||
type: string
|
type: string
|
||||||
user_id:
|
user_id:
|
||||||
type: integer
|
example: "7725"
|
||||||
user_key:
|
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
handler.SendMessage.response:
|
handler.SendMessage.response:
|
||||||
@ -359,7 +376,7 @@ definitions:
|
|||||||
quota_max:
|
quota_max:
|
||||||
type: integer
|
type: integer
|
||||||
scn_msg_id:
|
scn_msg_id:
|
||||||
type: integer
|
type: string
|
||||||
success:
|
success:
|
||||||
type: boolean
|
type: boolean
|
||||||
suppress_send:
|
suppress_send:
|
||||||
@ -480,6 +497,19 @@ definitions:
|
|||||||
uri:
|
uri:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
|
models.ChannelPreviewJSON:
|
||||||
|
properties:
|
||||||
|
channel_id:
|
||||||
|
type: string
|
||||||
|
description_name:
|
||||||
|
type: string
|
||||||
|
display_name:
|
||||||
|
type: string
|
||||||
|
internal_name:
|
||||||
|
type: string
|
||||||
|
owner_user_id:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
models.ChannelWithSubscriptionJSON:
|
models.ChannelWithSubscriptionJSON:
|
||||||
properties:
|
properties:
|
||||||
channel_id:
|
channel_id:
|
||||||
@ -577,6 +607,23 @@ definitions:
|
|||||||
timestamp_lastused:
|
timestamp_lastused:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
|
models.KeyTokenPreviewJSON:
|
||||||
|
properties:
|
||||||
|
all_channels:
|
||||||
|
type: boolean
|
||||||
|
channels:
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
|
keytoken_id:
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
owner_user_id:
|
||||||
|
type: string
|
||||||
|
permissions:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
models.KeyTokenWithTokenJSON:
|
models.KeyTokenWithTokenJSON:
|
||||||
properties:
|
properties:
|
||||||
all_channels:
|
all_channels:
|
||||||
@ -736,6 +783,13 @@ definitions:
|
|||||||
username:
|
username:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
|
models.UserPreviewJSON:
|
||||||
|
properties:
|
||||||
|
user_id:
|
||||||
|
type: string
|
||||||
|
username:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
host: simplecloudnotifier.de
|
host: simplecloudnotifier.de
|
||||||
info:
|
info:
|
||||||
contact: {}
|
contact: {}
|
||||||
@ -748,52 +802,90 @@ paths:
|
|||||||
description: All parameter can be set via query-parameter or the json body.
|
description: All parameter can be set via query-parameter or the json body.
|
||||||
Only UserID, UserKey and Title are required
|
Only UserID, UserKey and Title are required
|
||||||
parameters:
|
parameters:
|
||||||
- in: query
|
- example: test
|
||||||
|
in: query
|
||||||
|
name: channel
|
||||||
|
type: string
|
||||||
|
- example: This is a message
|
||||||
|
in: query
|
||||||
name: content
|
name: content
|
||||||
type: string
|
type: string
|
||||||
- in: query
|
- example: P3TNH8mvv14fm
|
||||||
|
in: query
|
||||||
|
name: key
|
||||||
|
type: string
|
||||||
|
- example: db8b0e6a-a08c-4646
|
||||||
|
in: query
|
||||||
name: msg_id
|
name: msg_id
|
||||||
type: string
|
type: string
|
||||||
- in: query
|
- enum:
|
||||||
|
- 0
|
||||||
|
- 1
|
||||||
|
- 2
|
||||||
|
example: 1
|
||||||
|
in: query
|
||||||
name: priority
|
name: priority
|
||||||
type: integer
|
type: integer
|
||||||
- in: query
|
- example: example-server
|
||||||
|
in: query
|
||||||
|
name: sender_name
|
||||||
|
type: string
|
||||||
|
- example: 1669824037
|
||||||
|
in: query
|
||||||
name: timestamp
|
name: timestamp
|
||||||
type: number
|
type: number
|
||||||
- in: query
|
- example: Hello World
|
||||||
|
in: query
|
||||||
name: title
|
name: title
|
||||||
type: string
|
type: string
|
||||||
- in: query
|
- example: "7725"
|
||||||
|
in: query
|
||||||
name: user_id
|
name: user_id
|
||||||
type: integer
|
|
||||||
- in: query
|
|
||||||
name: user_key
|
|
||||||
type: string
|
type: string
|
||||||
- description: ' '
|
- description: ' '
|
||||||
in: body
|
in: body
|
||||||
name: post_body
|
name: post_body
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/handler.SendMessage.combined'
|
$ref: '#/definitions/handler.SendMessage.combined'
|
||||||
- in: formData
|
- example: test
|
||||||
|
in: formData
|
||||||
|
name: channel
|
||||||
|
type: string
|
||||||
|
- example: This is a message
|
||||||
|
in: formData
|
||||||
name: content
|
name: content
|
||||||
type: string
|
type: string
|
||||||
- in: formData
|
- example: P3TNH8mvv14fm
|
||||||
|
in: formData
|
||||||
|
name: key
|
||||||
|
type: string
|
||||||
|
- example: db8b0e6a-a08c-4646
|
||||||
|
in: formData
|
||||||
name: msg_id
|
name: msg_id
|
||||||
type: string
|
type: string
|
||||||
- in: formData
|
- enum:
|
||||||
|
- 0
|
||||||
|
- 1
|
||||||
|
- 2
|
||||||
|
example: 1
|
||||||
|
in: formData
|
||||||
name: priority
|
name: priority
|
||||||
type: integer
|
type: integer
|
||||||
- in: formData
|
- example: example-server
|
||||||
|
in: formData
|
||||||
|
name: sender_name
|
||||||
|
type: string
|
||||||
|
- example: 1669824037
|
||||||
|
in: formData
|
||||||
name: timestamp
|
name: timestamp
|
||||||
type: number
|
type: number
|
||||||
- in: formData
|
- example: Hello World
|
||||||
|
in: formData
|
||||||
name: title
|
name: title
|
||||||
type: string
|
type: string
|
||||||
- in: formData
|
- example: "7725"
|
||||||
|
in: formData
|
||||||
name: user_id
|
name: user_id
|
||||||
type: integer
|
|
||||||
- in: formData
|
|
||||||
name: user_key
|
|
||||||
type: string
|
type: string
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
@ -1422,6 +1514,108 @@ paths:
|
|||||||
summary: Get a single message (untrimmed)
|
summary: Get a single message (untrimmed)
|
||||||
tags:
|
tags:
|
||||||
- API-v2
|
- API-v2
|
||||||
|
/api/v2/preview/channels/{cid}:
|
||||||
|
get:
|
||||||
|
operationId: api-channels-get-preview
|
||||||
|
parameters:
|
||||||
|
- description: ChannelID
|
||||||
|
in: path
|
||||||
|
name: cid
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/models.ChannelPreviewJSON'
|
||||||
|
"400":
|
||||||
|
description: supplied values/parameters cannot be parsed / are invalid
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/ginresp.apiError'
|
||||||
|
"401":
|
||||||
|
description: user is not authorized / has missing permissions
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/ginresp.apiError'
|
||||||
|
"404":
|
||||||
|
description: channel not found
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/ginresp.apiError'
|
||||||
|
"500":
|
||||||
|
description: internal server error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/ginresp.apiError'
|
||||||
|
summary: Get a single channel (similar to api-channels-get, but can be called
|
||||||
|
from anyone and only returns a subset of fields)
|
||||||
|
tags:
|
||||||
|
- API-v2
|
||||||
|
/api/v2/preview/keys/{kid}:
|
||||||
|
get:
|
||||||
|
operationId: api-tokenkeys-get-preview
|
||||||
|
parameters:
|
||||||
|
- description: TokenKeyID
|
||||||
|
in: path
|
||||||
|
name: kid
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/models.KeyTokenPreviewJSON'
|
||||||
|
"400":
|
||||||
|
description: supplied values/parameters cannot be parsed / are invalid
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/ginresp.apiError'
|
||||||
|
"401":
|
||||||
|
description: user is not authorized / has missing permissions
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/ginresp.apiError'
|
||||||
|
"404":
|
||||||
|
description: message not found
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/ginresp.apiError'
|
||||||
|
"500":
|
||||||
|
description: internal server error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/ginresp.apiError'
|
||||||
|
summary: Get a single key (similar to api-tokenkeys-get, but can be called from
|
||||||
|
anyone and only returns a subset of fields)
|
||||||
|
tags:
|
||||||
|
- API-v2
|
||||||
|
/api/v2/preview/users/{uid}:
|
||||||
|
get:
|
||||||
|
operationId: api-user-get-preview
|
||||||
|
parameters:
|
||||||
|
- description: UserID
|
||||||
|
in: path
|
||||||
|
name: uid
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/models.UserPreviewJSON'
|
||||||
|
"400":
|
||||||
|
description: supplied values/parameters cannot be parsed / are invalid
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/ginresp.apiError'
|
||||||
|
"401":
|
||||||
|
description: user is not authorized / has missing permissions
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/ginresp.apiError'
|
||||||
|
"404":
|
||||||
|
description: user not found
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/ginresp.apiError'
|
||||||
|
"500":
|
||||||
|
description: internal server error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/ginresp.apiError'
|
||||||
|
summary: Get a user (similar to api-user-get, but can be called from anyone
|
||||||
|
and only returns a subset of fields)
|
||||||
|
tags:
|
||||||
|
- API-v2
|
||||||
/api/v2/users:
|
/api/v2/users:
|
||||||
post:
|
post:
|
||||||
operationId: api-user-create
|
operationId: api-user-create
|
||||||
@ -2491,52 +2685,90 @@ paths:
|
|||||||
description: All parameter can be set via query-parameter or the json body.
|
description: All parameter can be set via query-parameter or the json body.
|
||||||
Only UserID, UserKey and Title are required
|
Only UserID, UserKey and Title are required
|
||||||
parameters:
|
parameters:
|
||||||
- in: query
|
- example: test
|
||||||
|
in: query
|
||||||
|
name: channel
|
||||||
|
type: string
|
||||||
|
- example: This is a message
|
||||||
|
in: query
|
||||||
name: content
|
name: content
|
||||||
type: string
|
type: string
|
||||||
- in: query
|
- example: P3TNH8mvv14fm
|
||||||
|
in: query
|
||||||
|
name: key
|
||||||
|
type: string
|
||||||
|
- example: db8b0e6a-a08c-4646
|
||||||
|
in: query
|
||||||
name: msg_id
|
name: msg_id
|
||||||
type: string
|
type: string
|
||||||
- in: query
|
- enum:
|
||||||
|
- 0
|
||||||
|
- 1
|
||||||
|
- 2
|
||||||
|
example: 1
|
||||||
|
in: query
|
||||||
name: priority
|
name: priority
|
||||||
type: integer
|
type: integer
|
||||||
- in: query
|
- example: example-server
|
||||||
|
in: query
|
||||||
|
name: sender_name
|
||||||
|
type: string
|
||||||
|
- example: 1669824037
|
||||||
|
in: query
|
||||||
name: timestamp
|
name: timestamp
|
||||||
type: number
|
type: number
|
||||||
- in: query
|
- example: Hello World
|
||||||
|
in: query
|
||||||
name: title
|
name: title
|
||||||
type: string
|
type: string
|
||||||
- in: query
|
- example: "7725"
|
||||||
|
in: query
|
||||||
name: user_id
|
name: user_id
|
||||||
type: integer
|
|
||||||
- in: query
|
|
||||||
name: user_key
|
|
||||||
type: string
|
type: string
|
||||||
- description: ' '
|
- description: ' '
|
||||||
in: body
|
in: body
|
||||||
name: post_body
|
name: post_body
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/handler.SendMessage.combined'
|
$ref: '#/definitions/handler.SendMessage.combined'
|
||||||
- in: formData
|
- example: test
|
||||||
|
in: formData
|
||||||
|
name: channel
|
||||||
|
type: string
|
||||||
|
- example: This is a message
|
||||||
|
in: formData
|
||||||
name: content
|
name: content
|
||||||
type: string
|
type: string
|
||||||
- in: formData
|
- example: P3TNH8mvv14fm
|
||||||
|
in: formData
|
||||||
|
name: key
|
||||||
|
type: string
|
||||||
|
- example: db8b0e6a-a08c-4646
|
||||||
|
in: formData
|
||||||
name: msg_id
|
name: msg_id
|
||||||
type: string
|
type: string
|
||||||
- in: formData
|
- enum:
|
||||||
|
- 0
|
||||||
|
- 1
|
||||||
|
- 2
|
||||||
|
example: 1
|
||||||
|
in: formData
|
||||||
name: priority
|
name: priority
|
||||||
type: integer
|
type: integer
|
||||||
- in: formData
|
- example: example-server
|
||||||
|
in: formData
|
||||||
|
name: sender_name
|
||||||
|
type: string
|
||||||
|
- example: 1669824037
|
||||||
|
in: formData
|
||||||
name: timestamp
|
name: timestamp
|
||||||
type: number
|
type: number
|
||||||
- in: formData
|
- example: Hello World
|
||||||
|
in: formData
|
||||||
name: title
|
name: title
|
||||||
type: string
|
type: string
|
||||||
- in: formData
|
- example: "7725"
|
||||||
|
in: formData
|
||||||
name: user_id
|
name: user_id
|
||||||
type: integer
|
|
||||||
- in: formData
|
|
||||||
name: user_key
|
|
||||||
type: string
|
type: string
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
@ -2569,47 +2801,85 @@ paths:
|
|||||||
description: All parameter can be set via query-parameter or form-data body.
|
description: All parameter can be set via query-parameter or form-data body.
|
||||||
Only UserID, UserKey and Title are required
|
Only UserID, UserKey and Title are required
|
||||||
parameters:
|
parameters:
|
||||||
- in: query
|
- example: test
|
||||||
|
in: query
|
||||||
|
name: channel
|
||||||
|
type: string
|
||||||
|
- example: This is a message
|
||||||
|
in: query
|
||||||
name: content
|
name: content
|
||||||
type: string
|
type: string
|
||||||
- in: query
|
- example: P3TNH8mvv14fm
|
||||||
|
in: query
|
||||||
|
name: key
|
||||||
|
type: string
|
||||||
|
- example: db8b0e6a-a08c-4646
|
||||||
|
in: query
|
||||||
name: msg_id
|
name: msg_id
|
||||||
type: string
|
type: string
|
||||||
- in: query
|
- enum:
|
||||||
|
- 0
|
||||||
|
- 1
|
||||||
|
- 2
|
||||||
|
example: 1
|
||||||
|
in: query
|
||||||
name: priority
|
name: priority
|
||||||
type: integer
|
type: integer
|
||||||
- in: query
|
- example: example-server
|
||||||
|
in: query
|
||||||
|
name: sender_name
|
||||||
|
type: string
|
||||||
|
- example: 1669824037
|
||||||
|
in: query
|
||||||
name: timestamp
|
name: timestamp
|
||||||
type: number
|
type: number
|
||||||
- in: query
|
- example: Hello World
|
||||||
|
in: query
|
||||||
name: title
|
name: title
|
||||||
type: string
|
type: string
|
||||||
- in: query
|
- example: "7725"
|
||||||
|
in: query
|
||||||
name: user_id
|
name: user_id
|
||||||
type: integer
|
|
||||||
- in: query
|
|
||||||
name: user_key
|
|
||||||
type: string
|
type: string
|
||||||
- in: formData
|
- example: test
|
||||||
|
in: formData
|
||||||
|
name: channel
|
||||||
|
type: string
|
||||||
|
- example: This is a message
|
||||||
|
in: formData
|
||||||
name: content
|
name: content
|
||||||
type: string
|
type: string
|
||||||
- in: formData
|
- example: P3TNH8mvv14fm
|
||||||
|
in: formData
|
||||||
|
name: key
|
||||||
|
type: string
|
||||||
|
- example: db8b0e6a-a08c-4646
|
||||||
|
in: formData
|
||||||
name: msg_id
|
name: msg_id
|
||||||
type: string
|
type: string
|
||||||
- in: formData
|
- enum:
|
||||||
|
- 0
|
||||||
|
- 1
|
||||||
|
- 2
|
||||||
|
example: 1
|
||||||
|
in: formData
|
||||||
name: priority
|
name: priority
|
||||||
type: integer
|
type: integer
|
||||||
- in: formData
|
- example: example-server
|
||||||
|
in: formData
|
||||||
|
name: sender_name
|
||||||
|
type: string
|
||||||
|
- example: 1669824037
|
||||||
|
in: formData
|
||||||
name: timestamp
|
name: timestamp
|
||||||
type: number
|
type: number
|
||||||
- in: formData
|
- example: Hello World
|
||||||
|
in: formData
|
||||||
name: title
|
name: title
|
||||||
type: string
|
type: string
|
||||||
- in: formData
|
- example: "7725"
|
||||||
|
in: formData
|
||||||
name: user_id
|
name: user_id
|
||||||
type: integer
|
|
||||||
- in: formData
|
|
||||||
name: user_key
|
|
||||||
type: string
|
type: string
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
|
Loading…
Reference in New Issue
Block a user