Now formatted correctly with eslint

This commit is contained in:
2026-03-04 00:41:02 +01:00
parent ad0e28bf84
commit 4e95ae77c5
99 changed files with 3231 additions and 1464 deletions

View File

@@ -2,9 +2,9 @@
* Root state definition and barrel exports for the NgRx store.
*
* Three feature slices:
* - **messages** chat messages, reactions, sync state
* - **users** online users, bans, roles, voice state
* - **rooms** servers / rooms, channels, search results
* - **messages** - chat messages, reactions, sync state
* - **users** - online users, bans, roles, voice state
* - **rooms** - servers / rooms, channels, search results
*/
import { isDevMode } from '@angular/core';
import { ActionReducerMap, MetaReducer } from '@ngrx/store';

View File

@@ -9,7 +9,12 @@
* handlers, and `dispatchIncomingMessage()` is the single entry point
* consumed by the `incomingMessages$` effect.
*/
import { Observable, of, from, EMPTY } from 'rxjs';
import {
Observable,
of,
from,
EMPTY
} from 'rxjs';
import { mergeMap } from 'rxjs/operators';
import { Action } from '@ngrx/store';
import { Message } from '../../core/models';
@@ -264,6 +269,7 @@ function handleMessageEdited(
content: event.content,
editedAt: event.editedAt
});
return of(
MessagesActions.editMessageSuccess({
messageId: event.messageId,

View File

@@ -10,9 +10,19 @@
*/
/* eslint-disable @typescript-eslint/member-ordering */
import { Injectable, inject } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import {
Actions,
createEffect,
ofType
} from '@ngrx/effects';
import { Store } from '@ngrx/store';
import { of, from, timer, Subject, EMPTY } from 'rxjs';
import {
of,
from,
timer,
Subject,
EMPTY
} from 'rxjs';
import {
map,
mergeMap,
@@ -78,6 +88,7 @@ export class MessagesSyncEffects {
count,
lastUpdated
} as any);
this.webrtc.sendToPeer(peerId, {
type: 'chat-inventory-request',
roomId: room.id
@@ -119,6 +130,7 @@ export class MessagesSyncEffects {
count,
lastUpdated
} as any);
this.webrtc.sendToPeer(pid, {
type: 'chat-inventory-request',
roomId: activeRoom.id

View File

@@ -4,7 +4,11 @@
* Action type strings follow the `[Messages] Event Name` convention and are
* generated automatically by NgRx from the `source` and event key.
*/
import { createActionGroup, emptyProps, props } from '@ngrx/store';
import {
createActionGroup,
emptyProps,
props
} from '@ngrx/store';
import { Message, Reaction } from '../../core/models';
export const MessagesActions = createActionGroup({

View File

@@ -10,10 +10,23 @@
*/
/* eslint-disable @typescript-eslint/member-ordering */
import { Injectable, inject } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import {
Actions,
createEffect,
ofType
} from '@ngrx/effects';
import { Store } from '@ngrx/store';
import { of, from, EMPTY } from 'rxjs';
import { mergeMap, catchError, withLatestFrom, switchMap } from 'rxjs/operators';
import {
of,
from,
EMPTY
} from 'rxjs';
import {
mergeMap,
catchError,
withLatestFrom,
switchMap
} from 'rxjs/operators';
import { v4 as uuidv4 } from 'uuid';
import { MessagesActions } from './messages.actions';
import { selectCurrentUser } from '../users/users.selectors';
@@ -24,10 +37,7 @@ import { TimeSyncService } from '../../core/services/time-sync.service';
import { AttachmentService } from '../../core/services/attachment.service';
import { Message, Reaction } from '../../core/models';
import { hydrateMessages } from './messages.helpers';
import {
dispatchIncomingMessage,
IncomingMessageContext
} from './messages-incoming.handlers';
import { dispatchIncomingMessage, IncomingMessageContext } from './messages-incoming.handlers';
@Injectable()
export class MessagesEffects {
@@ -65,7 +75,11 @@ export class MessagesEffects {
this.store.select(selectCurrentUser),
this.store.select(selectCurrentRoom)
),
mergeMap(([{ content, replyToId, channelId }, currentUser, currentRoom]) => {
mergeMap(([
{ content, replyToId, channelId },
currentUser,
currentRoom
]) => {
if (!currentUser || !currentRoom) {
return of(MessagesActions.sendMessageFailure({ error: 'Not connected to a room' }));
}
@@ -84,7 +98,8 @@ export class MessagesEffects {
};
this.db.saveMessage(message);
this.webrtc.broadcastMessage({ type: 'chat-message', message });
this.webrtc.broadcastMessage({ type: 'chat-message',
message });
return of(MessagesActions.sendMessageSuccess({ message }));
}),
@@ -116,10 +131,17 @@ export class MessagesEffects {
const editedAt = this.timeSync.now();
this.db.updateMessage(messageId, { content, editedAt });
this.webrtc.broadcastMessage({ type: 'message-edited', messageId, content, editedAt });
this.db.updateMessage(messageId, { content,
editedAt });
return of(MessagesActions.editMessageSuccess({ messageId, content, editedAt }));
this.webrtc.broadcastMessage({ type: 'message-edited',
messageId,
content,
editedAt });
return of(MessagesActions.editMessageSuccess({ messageId,
content,
editedAt }));
}),
catchError((error) =>
of(MessagesActions.editMessageFailure({ error: error.message }))
@@ -150,7 +172,8 @@ export class MessagesEffects {
}
this.db.updateMessage(messageId, { isDeleted: true });
this.webrtc.broadcastMessage({ type: 'message-deleted', messageId });
this.webrtc.broadcastMessage({ type: 'message-deleted',
messageId });
return of(MessagesActions.deleteMessageSuccess({ messageId }));
}),
@@ -182,7 +205,9 @@ export class MessagesEffects {
}
this.db.updateMessage(messageId, { isDeleted: true });
this.webrtc.broadcastMessage({ type: 'message-deleted', messageId, deletedBy: currentUser.id });
this.webrtc.broadcastMessage({ type: 'message-deleted',
messageId,
deletedBy: currentUser.id });
return of(MessagesActions.deleteMessageSuccess({ messageId }));
}),
@@ -211,7 +236,9 @@ export class MessagesEffects {
};
this.db.saveReaction(reaction);
this.webrtc.broadcastMessage({ type: 'reaction-added', messageId, reaction });
this.webrtc.broadcastMessage({ type: 'reaction-added',
messageId,
reaction });
return of(MessagesActions.addReactionSuccess({ reaction }));
})
@@ -256,7 +283,11 @@ export class MessagesEffects {
this.store.select(selectCurrentUser),
this.store.select(selectCurrentRoom)
),
mergeMap(([event, currentUser, currentRoom]: [any, any, any]) => {
mergeMap(([
event,
currentUser,
currentRoom]: [any, any, any
]) => {
const ctx: IncomingMessageContext = {
db: this.db,
webrtc: this.webrtc,

View File

@@ -56,7 +56,8 @@ export async function hydrateMessage(
): Promise<Message> {
const reactions = await db.getReactionsForMessage(msg.id);
return reactions.length > 0 ? { ...msg, reactions } : msg;
return reactions.length > 0 ? { ...msg,
reactions } : msg;
}
/** Hydrates an array of messages with their reactions. */
@@ -81,7 +82,9 @@ export async function buildInventoryItem(
): Promise<InventoryItem> {
const reactions = await db.getReactionsForMessage(msg.id);
return { id: msg.id, ts: getMessageTimestamp(msg), rc: reactions.length };
return { id: msg.id,
ts: getMessageTimestamp(msg),
rc: reactions.length };
}
/** Builds a local map of `{timestamp, reactionCount}` keyed by message ID. */
@@ -95,9 +98,11 @@ export async function buildLocalInventoryMap(
messages.map(async (msg) => {
const reactions = await db.getReactionsForMessage(msg.id);
map.set(msg.id, { ts: getMessageTimestamp(msg), rc: reactions.length });
map.set(msg.id, { ts: getMessageTimestamp(msg),
rc: reactions.length });
})
);
return map;
}
@@ -161,18 +166,22 @@ export async function mergeIncomingMessage(
const baseMessage = isNewer ? incoming : existing;
if (!baseMessage) {
return { message: incoming, changed };
return { message: incoming,
changed };
}
return {
message: { ...baseMessage, reactions },
message: { ...baseMessage,
reactions },
changed
};
}
if (!existing) {
return { message: incoming, changed: false };
return { message: incoming,
changed: false };
}
return { message: existing, changed: false };
return { message: existing,
changed: false };
}

View File

@@ -1,5 +1,9 @@
import { createReducer, on } from '@ngrx/store';
import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity';
import {
EntityState,
EntityAdapter,
createEntityAdapter
} from '@ngrx/entity';
import { Message } from '../../core/models';
import { MessagesActions } from './messages.actions';
@@ -30,7 +34,7 @@ export const initialState: MessagesState = messagesAdapter.getInitialState({
export const messagesReducer = createReducer(
initialState,
// Load messages clear stale messages when switching to a different room
// Load messages - clear stale messages when switching to a different room
on(MessagesActions.loadMessages, (state, { roomId }) => {
if (state.currentRoomId && state.currentRoomId !== roomId) {
return messagesAdapter.removeAll({
@@ -91,7 +95,8 @@ export const messagesReducer = createReducer(
messagesAdapter.updateOne(
{
id: messageId,
changes: { content, editedAt }
changes: { content,
editedAt }
},
state
)
@@ -102,7 +107,8 @@ export const messagesReducer = createReducer(
messagesAdapter.updateOne(
{
id: messageId,
changes: { isDeleted: true, content: '[Message deleted]' }
changes: { isDeleted: true,
content: '[Message deleted]' }
},
state
)
@@ -184,7 +190,8 @@ export const messagesReducer = createReducer(
}
}
return { ...message, reactions: combined };
return { ...message,
reactions: combined };
}
return message;

View File

@@ -1,8 +1,18 @@
/**
* Rooms store actions using `createActionGroup`.
*/
import { createActionGroup, emptyProps, props } from '@ngrx/store';
import { Room, RoomSettings, ServerInfo, RoomPermissions, Channel } from '../../core/models';
import {
createActionGroup,
emptyProps,
props
} from '@ngrx/store';
import {
Room,
RoomSettings,
ServerInfo,
RoomPermissions,
Channel
} from '../../core/models';
export const RoomsActions = createActionGroup({
source: 'Rooms',

View File

@@ -3,9 +3,17 @@
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-non-null-assertion */
import { Injectable, inject } from '@angular/core';
import { Router } from '@angular/router';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import {
Actions,
createEffect,
ofType
} from '@ngrx/effects';
import { Store } from '@ngrx/store';
import { of, from, EMPTY } from 'rxjs';
import {
of,
from,
EMPTY
} from 'rxjs';
import {
map,
mergeMap,
@@ -25,7 +33,12 @@ import { selectCurrentRoom } from './rooms.selectors';
import { DatabaseService } from '../../core/services/database.service';
import { WebRTCService } from '../../core/services/webrtc.service';
import { ServerDirectoryService } from '../../core/services/server-directory.service';
import { Room, RoomSettings, RoomPermissions, VoiceState } from '../../core/models';
import {
Room,
RoomSettings,
RoomPermissions,
VoiceState
} from '../../core/models';
import { NotificationAudioService, AppSound } from '../../core/services/notification-audio.service';
/** Build a minimal User object from signaling payload. */
@@ -285,11 +298,16 @@ export class RoomsEffects {
ofType(RoomsActions.deleteRoom),
withLatestFrom(this.store.select(selectCurrentUser), this.store.select(selectCurrentRoom)),
filter(
([, currentUser, currentRoom]) => !!currentUser && currentRoom?.hostId === currentUser.id
([
, currentUser,
currentRoom
]) => !!currentUser && currentRoom?.hostId === currentUser.id
),
switchMap(([{ roomId }]) => {
this.db.deleteRoom(roomId);
this.webrtc.broadcastMessage({ type: 'room-deleted', roomId });
this.webrtc.broadcastMessage({ type: 'room-deleted',
roomId });
this.webrtc.disconnectAll();
return of(RoomsActions.deleteRoomSuccess({ roomId }));
})
@@ -318,7 +336,11 @@ export class RoomsEffects {
this.actions$.pipe(
ofType(RoomsActions.updateRoomSettings),
withLatestFrom(this.store.select(selectCurrentUser), this.store.select(selectCurrentRoom)),
mergeMap(([{ settings }, currentUser, currentRoom]) => {
mergeMap(([
{ settings },
currentUser,
currentRoom
]) => {
if (!currentUser || !currentRoom) {
return of(RoomsActions.updateRoomSettingsFailure({ error: 'Not in a room' }));
}
@@ -377,15 +399,22 @@ export class RoomsEffects {
ofType(RoomsActions.updateRoomPermissions),
withLatestFrom(this.store.select(selectCurrentUser), this.store.select(selectCurrentRoom)),
filter(
([{ roomId }, currentUser, currentRoom]) =>
([
{ roomId },
currentUser,
currentRoom
]) =>
!!currentUser &&
!!currentRoom &&
currentRoom.id === roomId &&
currentRoom.hostId === currentUser.id
),
mergeMap(([{ roomId, permissions }, , currentRoom]) => {
mergeMap(([
{ roomId, permissions }, , currentRoom
]) => {
const updated: Partial<Room> = {
permissions: { ...(currentRoom!.permissions || {}), ...permissions } as RoomPermissions
permissions: { ...(currentRoom!.permissions || {}),
...permissions } as RoomPermissions
};
this.db.updateRoom(roomId, updated);
@@ -394,7 +423,9 @@ export class RoomsEffects {
type: 'room-permissions-update',
permissions: updated.permissions
} as any);
return of(RoomsActions.updateRoom({ roomId, changes: updated }));
return of(RoomsActions.updateRoom({ roomId,
changes: updated }));
})
)
);
@@ -404,7 +435,11 @@ export class RoomsEffects {
this.actions$.pipe(
ofType(RoomsActions.updateServerIcon),
withLatestFrom(this.store.select(selectCurrentUser), this.store.select(selectCurrentRoom)),
mergeMap(([{ roomId, icon }, currentUser, currentRoom]) => {
mergeMap(([
{ roomId, icon },
currentUser,
currentRoom
]) => {
if (!currentUser || !currentRoom || currentRoom.id !== roomId) {
return of(RoomsActions.updateServerIconFailure({ error: 'Not in room' }));
}
@@ -421,7 +456,8 @@ export class RoomsEffects {
}
const iconUpdatedAt = Date.now();
const changes: Partial<Room> = { icon, iconUpdatedAt };
const changes: Partial<Room> = { icon,
iconUpdatedAt };
this.db.updateRoom(roomId, changes);
// Broadcast to peers
@@ -431,7 +467,10 @@ export class RoomsEffects {
icon,
iconUpdatedAt
} as any);
return of(RoomsActions.updateServerIconSuccess({ roomId, icon, iconUpdatedAt }));
return of(RoomsActions.updateServerIconSuccess({ roomId,
icon,
iconUpdatedAt }));
})
)
);
@@ -491,7 +530,11 @@ export class RoomsEffects {
signalingMessages$ = createEffect(() =>
this.webrtc.onSignalingMessage.pipe(
withLatestFrom(this.store.select(selectCurrentUser), this.store.select(selectCurrentRoom)),
mergeMap(([message, currentUser, currentRoom]: [any, any, any]) => {
mergeMap(([
message,
currentUser,
currentRoom]: [any, any, any
]) => {
const myId = currentUser?.oderId || currentUser?.id;
const viewedServerId = currentRoom?.id;
@@ -533,7 +576,11 @@ export class RoomsEffects {
this.webrtc.onMessageReceived.pipe(
withLatestFrom(this.store.select(selectCurrentRoom), this.store.select(selectAllUsers)),
filter(([, room]) => !!room),
mergeMap(([event, currentRoom, allUsers]: [any, any, any[]]) => {
mergeMap(([
event,
currentRoom,
allUsers]: [any, any, any[]
]) => {
const room = currentRoom as Room;
switch (event.type) {
@@ -590,7 +637,8 @@ export class RoomsEffects {
return of(
UsersActions.userJoined({
user: buildSignalingUser(
{ oderId: userId, displayName: event.displayName || 'User' },
{ oderId: userId,
displayName: event.displayName || 'User' },
{
voiceState: {
isConnected: vs.isConnected ?? false,
@@ -608,7 +656,8 @@ export class RoomsEffects {
);
}
return of(UsersActions.updateVoiceState({ userId, voiceState: vs }));
return of(UsersActions.updateVoiceState({ userId,
voiceState: vs }));
}
// screen-state
@@ -621,7 +670,8 @@ export class RoomsEffects {
return of(
UsersActions.userJoined({
user: buildSignalingUser(
{ oderId: userId, displayName: event.displayName || 'User' },
{ oderId: userId,
displayName: event.displayName || 'User' },
{ screenShareState: { isSharing } }
)
})
@@ -700,7 +750,8 @@ export class RoomsEffects {
};
this.db.updateRoom(room.id, updates);
return of(RoomsActions.updateRoom({ roomId: room.id, changes: updates }));
return of(RoomsActions.updateRoom({ roomId: room.id,
changes: updates }));
})
);
}

View File

@@ -1,14 +1,31 @@
import { createReducer, on } from '@ngrx/store';
import { Room, ServerInfo, RoomSettings, Channel } from '../../core/models';
import {
Room,
ServerInfo,
RoomSettings,
Channel
} from '../../core/models';
import { RoomsActions } from './rooms.actions';
/** Default channels for a new server */
export function defaultChannels(): Channel[] {
return [
{ id: 'general', name: 'general', type: 'text', position: 0 },
{ id: 'random', name: 'random', type: 'text', position: 1 },
{ id: 'vc-general', name: 'General', type: 'voice', position: 0 },
{ id: 'vc-afk', name: 'AFK', type: 'voice', position: 1 }
{ id: 'general',
name: 'general',
type: 'text',
position: 0 },
{ id: 'random',
name: 'random',
type: 'text',
position: 1 },
{ id: 'vc-general',
name: 'General',
type: 'voice',
position: 0 },
{ id: 'vc-afk',
name: 'AFK',
type: 'voice',
position: 1 }
];
}
@@ -123,7 +140,8 @@ export const roomsReducer = createReducer(
})),
on(RoomsActions.createRoomSuccess, (state, { room }) => {
const enriched = { ...room, channels: room.channels || defaultChannels() };
const enriched = { ...room,
channels: room.channels || defaultChannels() };
return {
...state,
@@ -149,7 +167,8 @@ export const roomsReducer = createReducer(
})),
on(RoomsActions.joinRoomSuccess, (state, { room }) => {
const enriched = { ...room, channels: room.channels || defaultChannels() };
const enriched = { ...room,
channels: room.channels || defaultChannels() };
return {
...state,
@@ -181,7 +200,7 @@ export const roomsReducer = createReducer(
isConnected: false
})),
// View server just switch the viewed room, stay connected
// View server - just switch the viewed room, stay connected
on(RoomsActions.viewServer, (state) => ({
...state,
isConnecting: true,
@@ -189,7 +208,8 @@ export const roomsReducer = createReducer(
})),
on(RoomsActions.viewServerSuccess, (state, { room }) => {
const enriched = { ...room, channels: room.channels || defaultChannels() };
const enriched = { ...room,
channels: room.channels || defaultChannels() };
return {
...state,
@@ -264,7 +284,8 @@ export const roomsReducer = createReducer(
return {
...state,
currentRoom: { ...state.currentRoom, ...changes }
currentRoom: { ...state.currentRoom,
...changes }
};
}),
@@ -275,14 +296,17 @@ export const roomsReducer = createReducer(
return {
...state,
currentRoom: { ...state.currentRoom, icon, iconUpdatedAt }
currentRoom: { ...state.currentRoom,
icon,
iconUpdatedAt }
};
}),
// Receive room update
on(RoomsActions.receiveRoomUpdate, (state, { room }) => ({
...state,
currentRoom: state.currentRoom ? { ...state.currentRoom, ...room } : null
currentRoom: state.currentRoom ? { ...state.currentRoom,
...room } : null
})),
// Clear search results
@@ -309,7 +333,8 @@ export const roomsReducer = createReducer(
const existing = state.currentRoom.channels || defaultChannels();
const updatedChannels = [...existing, channel];
const updatedRoom = { ...state.currentRoom, channels: updatedChannels };
const updatedRoom = { ...state.currentRoom,
channels: updatedChannels };
return {
...state,
@@ -324,7 +349,8 @@ export const roomsReducer = createReducer(
const existing = state.currentRoom.channels || defaultChannels();
const updatedChannels = existing.filter(channel => channel.id !== channelId);
const updatedRoom = { ...state.currentRoom, channels: updatedChannels };
const updatedRoom = { ...state.currentRoom,
channels: updatedChannels };
return {
...state,
@@ -339,8 +365,10 @@ export const roomsReducer = createReducer(
return state;
const existing = state.currentRoom.channels || defaultChannels();
const updatedChannels = existing.map(channel => channel.id === channelId ? { ...channel, name } : channel);
const updatedRoom = { ...state.currentRoom, channels: updatedChannels };
const updatedChannels = existing.map(channel => channel.id === channelId ? { ...channel,
name } : channel);
const updatedRoom = { ...state.currentRoom,
channels: updatedChannels };
return {
...state,

View File

@@ -1,8 +1,17 @@
/**
* Users store actions using `createActionGroup`.
*/
import { createActionGroup, emptyProps, props } from '@ngrx/store';
import { User, BanEntry, VoiceState, ScreenShareState } from '../../core/models';
import {
createActionGroup,
emptyProps,
props
} from '@ngrx/store';
import {
User,
BanEntry,
VoiceState,
ScreenShareState
} from '../../core/models';
export const UsersActions = createActionGroup({
source: 'Users',

View File

@@ -3,13 +3,32 @@
*/
/* eslint-disable @typescript-eslint/member-ordering */
import { Injectable, inject } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import {
Actions,
createEffect,
ofType
} from '@ngrx/effects';
import { Store } from '@ngrx/store';
import { of, from, EMPTY } from 'rxjs';
import { map, mergeMap, catchError, withLatestFrom, tap, switchMap } from 'rxjs/operators';
import {
of,
from,
EMPTY
} from 'rxjs';
import {
map,
mergeMap,
catchError,
withLatestFrom,
tap,
switchMap
} from 'rxjs/operators';
import { v4 as uuidv4 } from 'uuid';
import { UsersActions } from './users.actions';
import { selectCurrentUser, selectCurrentUserId, selectHostId } from './users.selectors';
import {
selectCurrentUser,
selectCurrentUserId,
selectHostId
} from './users.selectors';
import { selectCurrentRoom } from '../rooms/rooms.selectors';
import { DatabaseService } from '../../core/services/database.service';
import { WebRTCService } from '../../core/services/webrtc.service';
@@ -67,7 +86,11 @@ export class UsersEffects {
this.store.select(selectCurrentUser),
this.store.select(selectCurrentRoom)
),
mergeMap(([{ userId }, currentUser, currentRoom]) => {
mergeMap(([
{ userId },
currentUser,
currentRoom
]) => {
if (!currentUser || !currentRoom)
return EMPTY;
@@ -99,7 +122,11 @@ export class UsersEffects {
this.store.select(selectCurrentUser),
this.store.select(selectCurrentRoom)
),
mergeMap(([{ userId, reason, expiresAt }, currentUser, currentRoom]) => {
mergeMap(([
{ userId, reason, expiresAt },
currentUser,
currentRoom
]) => {
if (!currentUser || !currentRoom)
return EMPTY;
@@ -127,7 +154,8 @@ export class UsersEffects {
reason
});
return of(UsersActions.banUserSuccess({ userId, ban }));
return of(UsersActions.banUserSuccess({ userId,
ban }));
})
)
);
@@ -171,7 +199,11 @@ export class UsersEffects {
this.store.select(selectHostId),
this.store.select(selectCurrentUserId)
),
mergeMap(([{ userId }, hostId, currentUserId]) =>
mergeMap(([
{ userId },
hostId,
currentUserId
]) =>
userId === hostId && currentUserId
? of(UsersActions.updateHost({ userId: currentUserId }))
: EMPTY

View File

@@ -1,5 +1,9 @@
import { createReducer, on } from '@ngrx/store';
import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity';
import {
EntityState,
EntityAdapter,
createEntityAdapter
} from '@ngrx/entity';
import { User, BanEntry } from '../../core/models';
import { UsersActions } from './users.actions';