Skip to content
This repository has been archived by the owner on Jan 12, 2022. It is now read-only.

Commit

Permalink
Release 1.3.9 (#99)
Browse files Browse the repository at this point in the history
  • Loading branch information
Casper authored Mar 3, 2021
2 parents d951b8c + 411377c commit 2056f7d
Show file tree
Hide file tree
Showing 33 changed files with 180 additions and 78 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

**If you need help updating/installing or have found any bugs, please join [our Discord server](https://discord.gg/eGnrPqEH7U) or open a [GitHub issue here](https://github.com/Dev-CasperTheGhost/snaily-cadv3/issues/new/choose)**

## 1.3.9

- Minor code improvements
- Added `rank` to officers, assignable by moderators, admins and owners
- Added `supervisor` rank to users, this rank can only manage officers

## 1.3.8

- Fixed small bug not being able to search for medical records
Expand Down
18 changes: 13 additions & 5 deletions client/src/components/AuthRoute.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ interface Props {
isAuth: boolean;
loading: boolean;
path: string;
requirement?: "admin" | "leo" | "dispatch" | "tow" | "ems_fd";
requirement?: "admin" | "leo" | "dispatch" | "tow" | "ems_fd" | "supervisor";
user: User;
}

Expand All @@ -24,25 +24,33 @@ const AuthRoute: React.FC<Props> = ({ Component, loading, isAuth, path, user, re
if (requirement && !loading && isAuth) {
switch (requirement) {
case "leo":
if (user?.leo !== "1") {
if (user?.leo === "0") {
return history.push("/forbidden");
}
break;
case "dispatch":
if (user?.dispatch !== "1") {
if (user?.dispatch === "0") {
return history.push("/forbidden");
}
break;
case "tow":
if (user?.tow !== "1") {
if (user?.tow === "0") {
history.push("/forbidden");
}
break;
case "ems_fd":
if (user?.ems_fd !== "1") {
if (user?.ems_fd === "0") {
history.push("/forbidden");
}
break;
case "supervisor":
if (user.supervisor === "1") break;

if (!adminRanks.includes(user.rank)) {
history.push("/forbidden");
}
break;

case "admin":
if (!adminRanks.includes(user.rank)) {
history.push("/forbidden");
Expand Down
14 changes: 13 additions & 1 deletion client/src/components/leo/ModalButtons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { Link } from "react-router-dom";
import socket from "../../lib/socket";
import Officer from "../../interfaces/Officer";
import lang from "../../language.json";
import User from "../../interfaces/User";
import { adminRanks } from "../AuthRoute";

export interface MButton {
name: string;
Expand Down Expand Up @@ -46,9 +48,10 @@ const modalButtons: MButton[] = [

interface Props {
activeOfficer: Officer | null;
user: User | null;
}

const ModalButtons: React.FC<Props> = ({ activeOfficer }) => {
const ModalButtons: React.FC<Props> = ({ user, activeOfficer }) => {
function panicButton() {
socket.emit("PANIC_BUTTON", activeOfficer);
}
Expand All @@ -63,6 +66,15 @@ const ModalButtons: React.FC<Props> = ({ activeOfficer }) => {
<Link to="/leo/my-officers" className="btn btn-primary col-md-2">
{lang.officers.my_officers}
</Link>
{user?.supervisor === "1" ? (
<Link to="/admin/manage/officers" className="btn btn-primary col-md-2">
Manage officers
</Link>
) : adminRanks.includes(`${user?.rank}`) ? (
<Link to="/admin/manage/officers" className="btn btn-secondary col-md-2">
Manage officers
</Link>
) : null}

{/* modals */}
{modalButtons.map((mButton: MButton, idx: number) => {
Expand Down
4 changes: 2 additions & 2 deletions client/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -186,12 +186,12 @@ ReactDOM.render(
/>
<AuthRoute
path="/admin/manage/officers/:id"
requirement="admin"
requirement="supervisor"
Component={ManageOfficerPage}
/>
<AuthRoute
path="/admin/manage/officers"
requirement="admin"
requirement="supervisor"
Component={OfficersManagementPage}
/>

Expand Down
1 change: 1 addition & 0 deletions client/src/interfaces/Officer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ interface Officer {
officer_name: string;
officer_dept: string;
callsign?: string;
rank: string;
}

export default Officer;
1 change: 1 addition & 0 deletions client/src/interfaces/User.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ interface User {
rank: "owner" | "admin" | "moderator" | "user";
leo: Perm;
ems_fd: Perm;
supervisor: Perm;
dispatch: Perm;
tow: Perm;
banned: Perm;
Expand Down
4 changes: 4 additions & 0 deletions client/src/lib/Logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ class Logger {
error(type: string, error: string): void {
console.error(`[${type.toUpperCase()}][${this.now()}]: ${error}`);
}

warn(type: string, message: string): void {
console.warn(`[${type.toUpperCase()}][${this.now()}]: ${message}`);
}
}

export default new Logger();
1 change: 1 addition & 0 deletions client/src/lib/actions/admin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,7 @@ export const getOfficerById = (id: string) => async (dispatch: Dispatch<IDispatc

export interface UpdateOfficerData {
callsign: string;
rank: string;
}

export const updateOfficerById = (id: string, data: UpdateOfficerData) => async (
Expand Down
22 changes: 22 additions & 0 deletions client/src/pages/admin/management/members/manage-member.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ const ManageMember: React.FC<Props> = ({
const id = match.params.id;
const [rank, setRank] = React.useState("");
const [leo, setLeo] = React.useState("");
const [supervisor, setSupervisor] = React.useState("");
const [dispatch, setDispatch] = React.useState("");
const [emsFd, setEmsFd] = React.useState("");
const [tow, setTow] = React.useState("");
Expand All @@ -61,6 +62,7 @@ const ManageMember: React.FC<Props> = ({
setDispatch(member?.dispatch);
setEmsFd(member?.ems_fd);
setTow(member?.tow);
setSupervisor(member.supervisor);
}
}, [member]);

Expand All @@ -73,6 +75,7 @@ const ManageMember: React.FC<Props> = ({
dispatch,
emsFd,
tow,
supervisor,
});
}

Expand Down Expand Up @@ -144,6 +147,25 @@ const ManageMember: React.FC<Props> = ({
<option value="1">{lang.global.yes}</option>
</select>
</div>
<div className="mb-3">
<label className="form-label" htmlFor="leo">
LEO Supervisor
</label>
<select
id="supervisor"
onChange={(e) => setSupervisor(e.target.value)}
className="form-control bg-dark border-dark text-light"
>
<option value={member?.supervisor}>
{member?.supervisor === "1" ? lang.global.yes : lang.global.no}
</option>
<option disabled value="">
--------
</option>
<option value="0">{lang.global.no}</option>
<option value="1">{lang.global.yes}</option>
</select>
</div>
<div className="mb-3">
<label className="form-label" htmlFor="dispatch">
{lang.auth.account.dispatch_access}
Expand Down
15 changes: 15 additions & 0 deletions client/src/pages/admin/management/officers/manage-officer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,23 @@ const ManageOfficerPage: React.FC<Props> = ({
}) => {
const { id } = useParams<{ id: string }>();
const [callSign, setCallSign] = React.useState(officer?.callsign || "");
const [rank, setRank] = React.useState(officer?.rank || "");

React.useEffect(() => {
getOfficerById(id);
}, [id, getOfficerById]);

React.useEffect(() => {
setCallSign(officer?.callsign || "");
setRank(officer?.rank || "");
}, [officer]);

function onSubmit(e: React.FormEvent) {
e.preventDefault();

updateOfficerById(id, {
callsign: callSign,
rank,
});
}

Expand All @@ -61,6 +64,18 @@ const ManageOfficerPage: React.FC<Props> = ({
className="form-control bg-dark border-dark text-light"
/>
</div>
<div className="mb-3">
<label className="form-label" htmlFor="tow">
Rank
</label>

<input
placeholder="rank"
value={rank}
onChange={(e) => setRank(e.currentTarget.value)}
className="form-control bg-dark border-dark text-light"
/>
</div>

<div className="float-end">
<Link to="/admin/manage/officers" className="btn btn-danger mx-2">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ const OfficersManagementPage: React.FC<Props> = ({ officers, message, getAllOffi
<Span>Callsign: </Span>
{officer.callsign || "None set"}
</Item>
<Item id="rank">
<Span>Rank: </Span>
{officer.rank || "None set"}
</Item>
</div>
</div>

Expand Down
6 changes: 4 additions & 2 deletions client/src/pages/leo/dash.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,15 @@ import Officer from "../../interfaces/Officer";
import { playSound } from "../../lib/functions";
import { getPenalCodes } from "../../lib/actions/admin";
import { useLocation } from "react-router-dom";
import { Perm } from "../../interfaces/User";
import User, { Perm } from "../../interfaces/User";
import CadInfo from "../../interfaces/CadInfo";

interface Props {
aop: string;
message: Message;
activeOfficer: Officer | null;
cadInfo: CadInfo;
user: User | null;
getPenalCodes: () => void;
}

Expand Down Expand Up @@ -108,7 +109,7 @@ const LeoDash: React.FC<Props> = (props) => {
<span>{new Date(time).toLocaleString()}</span>
</div>
<div className="card-body row gap-2 px-4">
<ModalButtons activeOfficer={props.activeOfficer} />
<ModalButtons user={props.user} activeOfficer={props.activeOfficer} />
</div>
<div className="card-footer row gap-2 pl-2 px-4">
<Statuses />
Expand Down Expand Up @@ -146,6 +147,7 @@ const mapToProps = (state: State) => ({
message: state.global.message,
activeOfficer: state.officers.activeOfficer,
cadInfo: state.global.cadInfo,
user: state.auth.user,
});

export default connect(mapToProps, { getPenalCodes })(React.memo(LeoDash));
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "snaily-cadv3",
"version": "1.3.8",
"version": "1.3.9",
"description": "An open source Computer Aided Dispatch (CAD) for FiveM, this is a web based integration for communities who love police roleplaying and dispatching. ",
"main": "index.js",
"scripts": {
Expand Down
11 changes: 8 additions & 3 deletions server/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
"browser": true,
"es2021": true
},
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 12,
Expand All @@ -15,12 +19,13 @@
"semi": ["error", "always"],
"no-multi-spaces": ["error"],
"eqeqeq": ["warn", "always"],
"no-unused-vars": ["error"],
"no-duplicate-case": ["error"],
"no-extra-semi": ["error"],
"no-unreachable": ["error"],
"default-case": ["warn"],
"default-case-last": ["error"],
"no-useless-catch": ["warn"]
"no-useless-catch": ["warn"],
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": ["error"]
}
}
2 changes: 1 addition & 1 deletion server/src/hooks/useAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ async function useAuth(req: IRequest, res: Response, next: NextFunction): Promis
try {
const vToken = jwt.verify(token, secret) as IUser;
const user = await processQuery(
"SELECT `id`, `username`, `rank`, `leo`, `ems_fd`, `dispatch`, `tow`, `banned`, `ban_reason`, `whitelist_status`, `steam_id`, `avatar_url` FROM `users` WHERE `id` = ?",
"SELECT `id`, `username`, `rank`, `leo`, `ems_fd`, `dispatch`, `tow`, `banned`, `ban_reason`, `whitelist_status`, `steam_id`, `avatar_url`, `supervisor` FROM `users` WHERE `id` = ?",
[vToken.id]
);

Expand Down
11 changes: 1 addition & 10 deletions server/src/hooks/useMarkdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,6 @@ const dompurify = createDompurify(window);
export default function (data: string): string {
return dompurify.sanitize(marked(data), {
FORBID_ATTR: ["style", "onerror", "onload"],
FORBID_TAGS: [
"script",
"audio",
"video",
"style",
"iframe",
"textarea",
"frame",
"frameset",
],
FORBID_TAGS: ["script", "audio", "video", "style", "iframe", "textarea", "frame", "frameset", "table", "td", "th"],
});
}
23 changes: 19 additions & 4 deletions server/src/hooks/usePermission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { processQuery } from "../lib/database";
import Logger from "../lib/Logger";

// rank, leo, ems_fd, dispatch, tow
type UserPermsArr = [RanksType, Perm, Perm, Perm, Perm];
type UserPermsArr = [RanksType, Perm, Perm, Perm, Perm, Perm];
type Permissions = RanksType | "leo" | "ems_fd" | "dispatch" | "tow";

const usePermission = (perms: Permissions[]) => async (
Expand All @@ -15,12 +15,19 @@ const usePermission = (perms: Permissions[]) => async (
next: NextFunction,
): Promise<void | Response> => {
try {
const user = await processQuery<IUser[]>(
"SELECT `rank`, `leo`, `dispatch`, `tow`, `ems_fd` FROM `users` WHERE `id` = ?",
const user = await processQuery<IUser>(
"SELECT `rank`, `leo`, `dispatch`, `tow`, `ems_fd`, `supervisor` FROM `users` WHERE `id` = ?",
[req.user?.id],
);

const userPerms: UserPermsArr = [user[0].rank, user[0].leo, user[0].ems_fd, user[0].dispatch, user[0].tow];
const userPerms: UserPermsArr = [
user[0].rank,
user[0].leo,
user[0].ems_fd,
user[0].dispatch,
user[0].tow,
user[0].supervisor,
];

if (!user[0]) {
return res.json({
Expand Down Expand Up @@ -70,6 +77,14 @@ const usePermission = (perms: Permissions[]) => async (
}
break;
}
case "supervisor": {
if (userPerms[5] === "0") {
invalidPerms.push("supervisor");
} else {
invalid = false;
}
break;
}
default: {
// 0 = rank | defaults to 'default'
if (!RanksArr.includes(user[0].rank)) {
Expand Down
Loading

0 comments on commit 2056f7d

Please sign in to comment.