diff --git a/client/.env b/client/.env
deleted file mode 100644
index 57f426c..0000000
--- a/client/.env
+++ /dev/null
@@ -1,4 +0,0 @@
-VITE_API_URL=http://localhost:3001
-# VITE_API_URL=http://192.168.1.171:3001
-# VITE_API_URL=https://crm.stream.graff.tech/api
-VITE_STREAM_URL=https://stream.graff.tech
diff --git a/client/.env.development b/client/.env.development
new file mode 100644
index 0000000..e1eb4c9
--- /dev/null
+++ b/client/.env.development
@@ -0,0 +1,2 @@
+VITE_API_URL=http://localhost:3001
+VITE_STREAM_URL=https://stream.graff.tech
diff --git a/client/.env.production b/client/.env.production
new file mode 100644
index 0000000..bef2196
--- /dev/null
+++ b/client/.env.production
@@ -0,0 +1,2 @@
+VITE_API_URL=https://crm.stream.graff.tech/api
+VITE_STREAM_URL=https://stream.graff.tech
diff --git a/client/src/components/Card.tsx b/client/src/components/Card.tsx
index a31e0f3..2c5fd0e 100644
--- a/client/src/components/Card.tsx
+++ b/client/src/components/Card.tsx
@@ -97,7 +97,7 @@ function Card({
{manager ? (

diff --git a/client/src/components/Input.tsx b/client/src/components/Input.tsx
index 5cae857..2237bb8 100644
--- a/client/src/components/Input.tsx
+++ b/client/src/components/Input.tsx
@@ -4,6 +4,7 @@ import { useEffect, useState } from "react";
interface InputProps {
type?: "text" | "email" | "password" | "time" | "tel";
+ value?: string;
placeholder?: string;
autoFocus?: boolean;
required?: boolean;
@@ -16,11 +17,12 @@ interface InputProps {
function Input({
type = "text",
+ value,
placeholder,
autoFocus,
required,
readOnly,
- defaultValue,
+ defaultValue = "",
className,
handleChange,
handleFocus,
@@ -30,11 +32,15 @@ function Input({
replacement: { _: /\d/ },
});
- const [value, setValue] = useState
(defaultValue || "");
+ const [_value, setValue] = useState(value || defaultValue || "");
useEffect(() => {
- handleChange && handleChange(value);
- }, [value]);
+ if (!_value || !handleChange) {
+ return;
+ }
+
+ handleChange(_value);
+ }, [_value]);
return (
setValue(e.target.value)}
onFocus={handleFocus}
className={`px-3 py-2.5 outline-none rounded-lg border border-[#DAE0E5] focus:border-[#49A1F5] transition-colors text-sm ${className}`}
diff --git a/client/src/components/Managers.tsx b/client/src/components/Managers.tsx
index 80ee7fb..e74fe35 100644
--- a/client/src/components/Managers.tsx
+++ b/client/src/components/Managers.tsx
@@ -30,7 +30,7 @@ function Managers() {

diff --git a/client/src/components/SelectUser.tsx b/client/src/components/SelectUser.tsx
index dbdb106..5f2174a 100644
--- a/client/src/components/SelectUser.tsx
+++ b/client/src/components/SelectUser.tsx
@@ -36,10 +36,7 @@ function SelectUser({
>

diff --git a/client/src/components/modals/CreateBuildModal.tsx b/client/src/components/modals/CreateBuildModal.tsx
new file mode 100644
index 0000000..b0b37fa
--- /dev/null
+++ b/client/src/components/modals/CreateBuildModal.tsx
@@ -0,0 +1,84 @@
+import { FormEvent, useState } from "react";
+import Button from "../Button";
+import Input from "../Input";
+import api from "../../utils/api";
+import IError from "../../types/IError";
+import IBuild from "../../types/IBuild";
+import useModalStore from "../../stores/useModalStore";
+
+interface Props {
+ companyId: string;
+}
+
+function CreateBuildModal({ companyId }: Props) {
+ const [name, setName] = useState
("");
+ const [build, setBuild] = useState("");
+ const [sessionLimit, setSessionLimit] = useState(1);
+ const { setModal } = useModalStore();
+
+ function handleSubmit(e: FormEvent) {
+ e.preventDefault();
+
+ addBuild();
+ }
+
+ async function addBuild() {
+ try {
+ const result: IBuild | IError = await api
+ .post(`admin/builds`, {
+ json: {
+ companyId,
+ name,
+ build,
+ sessionLimit,
+ },
+ })
+ .json();
+
+ if ("error" in result) {
+ alert(result.error);
+ return;
+ }
+
+ window.location.reload();
+ } catch (error) {
+ alert((error as Error).message);
+ }
+ }
+
+ return (
+
+ );
+}
+
+export default CreateBuildModal;
diff --git a/client/src/components/modals/CreateCompanyModal.tsx b/client/src/components/modals/CreateCompanyModal.tsx
new file mode 100644
index 0000000..f80f003
--- /dev/null
+++ b/client/src/components/modals/CreateCompanyModal.tsx
@@ -0,0 +1,62 @@
+import { FormEvent, useState } from "react";
+import Button from "../Button";
+import Input from "../Input";
+import api from "../../utils/api";
+import ICompany from "../../types/ICompany";
+import IError from "../../types/IError";
+import useModalStore from "../../stores/useModalStore";
+
+function CreateCompanyModal() {
+ const [name, setName] = useState("");
+ const { setModal } = useModalStore();
+
+ function handleSubmit(e: FormEvent) {
+ e.preventDefault();
+
+ addComapny();
+ }
+
+ async function addComapny() {
+ try {
+ const result: ICompany | IError = await api
+ .post(`admin/companies`, {
+ json: {
+ name,
+ },
+ })
+ .json();
+
+ if ("error" in result) {
+ alert(result.error);
+ return;
+ }
+
+ window.location.reload();
+ } catch (error) {
+ alert((error as Error).message);
+ }
+ }
+
+ return (
+
+ );
+}
+
+export default CreateCompanyModal;
diff --git a/client/src/components/modals/CreateUserModal.tsx b/client/src/components/modals/CreateUserModal.tsx
new file mode 100644
index 0000000..360522d
--- /dev/null
+++ b/client/src/components/modals/CreateUserModal.tsx
@@ -0,0 +1,159 @@
+/* eslint-disable react-hooks/exhaustive-deps */
+import { ChangeEvent, FormEvent, useEffect, useState } from "react";
+import Button from "../Button";
+import Input from "../Input";
+import api from "../../utils/api";
+import IError from "../../types/IError";
+import IBuild from "../../types/IBuild";
+import { Transition } from "react-transition-group";
+import SpinnerIcon from "../icons/SpinnerIcon";
+import useModalStore from "../../stores/useModalStore";
+
+interface Props {
+ companyId: string;
+}
+
+function CreateUserModal({ companyId }: Props) {
+ const [name, setName] = useState("");
+ const [username, setUsername] = useState("");
+ const [password, setPassword] = useState("");
+ const [builds, setBuilds] = useState();
+ const [role, setRole] = useState("manager");
+ const [buildIds, setBuildIds] = useState([]);
+ const [isLoading, setIsLoading] = useState(true);
+ const { setModal } = useModalStore();
+
+ async function getBuilds() {
+ try {
+ const result: IBuild[] | IError = await api
+ .get(`admin/builds?companyId=${companyId}`)
+ .json();
+
+ if ("error" in result) {
+ alert(result.error);
+ return;
+ }
+
+ setBuilds(result);
+ } catch (error) {
+ alert((error as Error).message);
+ }
+ }
+
+ function handleSubmit(e: FormEvent) {
+ e.preventDefault();
+
+ addUser();
+ }
+
+ async function addUser() {
+ try {
+ const result: IBuild | IError = await api
+ .post(`admin/users`, {
+ json: {
+ companyId,
+ name,
+ username,
+ password,
+ role,
+ buildIds,
+ },
+ })
+ .json();
+
+ if ("error" in result) {
+ alert(result.error);
+ return;
+ }
+
+ window.location.reload();
+ } catch (error) {
+ alert((error as Error).message);
+ }
+ }
+
+ function handleChangeBuilds(e: ChangeEvent) {
+ const buildValues = [...e.target.selectedOptions].map(
+ (option) => option.value
+ );
+
+ setBuildIds(buildValues);
+ }
+
+ useEffect(() => {
+ getBuilds();
+ }, []);
+
+ useEffect(() => {
+ if (!builds) {
+ return;
+ }
+
+ setIsLoading(false);
+ }, [builds]);
+
+ return (
+
+
Создание пользователя
+
+
+
+ {(state) => (
+
+
+
+ )}
+
+
+ );
+}
+
+export default CreateUserModal;
diff --git a/client/src/components/modals/EditUserModal.tsx b/client/src/components/modals/EditUserModal.tsx
new file mode 100644
index 0000000..aeb334e
--- /dev/null
+++ b/client/src/components/modals/EditUserModal.tsx
@@ -0,0 +1,184 @@
+/* eslint-disable react-hooks/exhaustive-deps */
+import { ChangeEvent, FormEvent, useEffect, useState } from "react";
+import Button from "../Button";
+import Input from "../Input";
+import api from "../../utils/api";
+import IError from "../../types/IError";
+import IBuild from "../../types/IBuild";
+import { Transition } from "react-transition-group";
+import SpinnerIcon from "../icons/SpinnerIcon";
+import IUser from "../../types/IUser";
+import useModalStore from "../../stores/useModalStore";
+
+interface Props {
+ companyId: string;
+ userId: string;
+}
+
+function EditUserModal({ companyId, userId }: Props) {
+ const [user, setUser] = useState();
+ const [name, setName] = useState("");
+ const [username, setUsername] = useState("");
+ // const [password, setPassword] = useState("");
+ const [builds, setBuilds] = useState();
+ const [role, setRole] = useState("manager");
+ const [buildIds, setBuildIds] = useState([]);
+ const [isLoading, setIsLoading] = useState(true);
+ const { setModal } = useModalStore();
+
+ async function getUser() {
+ try {
+ const result: IUser | IError = await api
+ .get(`admin/users/${userId}`)
+ .json();
+
+ if ("error" in result) {
+ alert(result.error);
+ return;
+ }
+
+ setUser(result);
+ setName(result.name);
+ setUsername(result.username);
+ setRole(result.role);
+
+ if (result.buildIds) {
+ setBuildIds(result.buildIds);
+ }
+ } catch (error) {
+ alert((error as Error).message);
+ }
+ }
+
+ async function getBuilds() {
+ try {
+ const result: IBuild[] | IError = await api
+ .get(`admin/builds?companyId=${companyId}`)
+ .json();
+
+ if ("error" in result) {
+ alert(result.error);
+ return;
+ }
+
+ setBuilds(result);
+ } catch (error) {
+ alert((error as Error).message);
+ }
+ }
+
+ function handleSubmit(e: FormEvent) {
+ e.preventDefault();
+
+ updateUser();
+ }
+
+ async function updateUser() {
+ try {
+ const result: IBuild | IError = await api
+ .put(`admin/users/${userId}`, {
+ json: {
+ name,
+ username,
+ role,
+ buildIds,
+ },
+ })
+ .json();
+
+ if ("error" in result) {
+ alert(result.error);
+ return;
+ }
+
+ window.location.reload();
+ } catch (error) {
+ alert((error as Error).message);
+ }
+ }
+
+ function handleChangeBuilds(e: ChangeEvent) {
+ const buildValues = [...e.target.selectedOptions].map(
+ (option) => option.value
+ );
+
+ setBuildIds(buildValues);
+ }
+
+ useEffect(() => {
+ getUser();
+ getBuilds();
+ }, []);
+
+ useEffect(() => {
+ if (!user || !builds || !buildIds) {
+ return;
+ }
+
+ setIsLoading(false);
+ }, [user, builds, buildIds]);
+
+ return (
+
+
Редактирование
+
+
+
+ {(state) => (
+
+
+
+ )}
+
+
+ );
+}
+
+export default EditUserModal;
diff --git a/client/src/main.tsx b/client/src/main.tsx
index 665a620..910fe61 100644
--- a/client/src/main.tsx
+++ b/client/src/main.tsx
@@ -8,6 +8,8 @@ import LoginPage from "./pages/LoginPage.tsx";
import RegistrationPage from "./pages/RegistrationPage.tsx";
import RegistrationCompanyPage from "./pages/RegistrationCompanyPage.tsx";
import RegistrationManagerPage from "./pages/RegistrationManagerPage.tsx";
+import AdminPage from "./pages/AdminPage.tsx";
+import AdminCompanyPage from "./pages/AdminCompanyPage.tsx";
const router = createBrowserRouter([
{
@@ -48,6 +50,25 @@ const router = createBrowserRouter([
},
],
},
+ {
+ path: "admin",
+ element: ,
+ children: [
+ {
+ index: true,
+ element: ,
+ },
+ {
+ path: ":companyId",
+ children: [
+ {
+ index: true,
+ element: ,
+ },
+ ],
+ },
+ ],
+ },
],
},
]);
diff --git a/client/src/pages/AdminCompanyPage.tsx b/client/src/pages/AdminCompanyPage.tsx
new file mode 100644
index 0000000..3943c72
--- /dev/null
+++ b/client/src/pages/AdminCompanyPage.tsx
@@ -0,0 +1,194 @@
+/* eslint-disable react-hooks/exhaustive-deps */
+import { useEffect, useState } from "react";
+import api from "../utils/api";
+import IError from "../types/IError";
+import Button from "../components/Button";
+import useModalStore from "../stores/useModalStore";
+import ModalContainer from "../components/ModalContainer";
+import { useNavigate, useParams } from "react-router-dom";
+import IBuild from "../types/IBuild";
+import CreateBuildModal from "../components/modals/CreateBuildModal";
+import IUser from "../types/IUser";
+import CreateUserModal from "../components/modals/CreateUserModal";
+import { Transition } from "react-transition-group";
+import SpinnerIcon from "../components/icons/SpinnerIcon";
+import MoreIcon from "../components/icons/MoreIcon";
+import EditUserModal from "../components/modals/EditUserModal";
+
+function AdminCompanyPage() {
+ const { companyId } = useParams();
+ const [builds, setBuilds] = useState();
+ const [users, setUsers] = useState();
+ const { setModal } = useModalStore();
+ const navigate = useNavigate();
+ const [isLoading, setIsLoading] = useState(true);
+
+ async function getBuilds() {
+ if (!companyId) return;
+
+ try {
+ const result: IBuild[] | IError = await api
+ .get(`admin/builds?companyId=${companyId}`)
+ .json();
+
+ if ("error" in result) {
+ alert(result.error);
+ return;
+ }
+
+ setBuilds(result);
+ } catch (error) {
+ alert((error as Error).message);
+ }
+ }
+
+ async function getUsers() {
+ if (!companyId) return;
+
+ try {
+ const result: IUser[] | IError = await api
+ .get(`admin/users?companyId=${companyId}`)
+ .json();
+
+ if ("error" in result) {
+ alert(result.error);
+ return;
+ }
+
+ setUsers(result);
+ } catch (error) {
+ alert((error as Error).message);
+ }
+ }
+
+ useEffect(() => {
+ getBuilds();
+ getUsers();
+ }, []);
+
+ useEffect(() => {
+ if (!users || !builds) {
+ return;
+ }
+
+ setIsLoading(false);
+ }, [users, builds]);
+
+ return (
+
+
+
+
+
+
+
Жилые комплексы
+
+
+
+ {builds &&
+ builds.map((build) => (
+
+
![]()
+
+
{build.name}
+
+ Сборка приложения:{" "}
+ {build.build}
+
+
+ Кол-во слотов:{" "}
+ {build.sessionLimit}
+
+
+
+ ))}
+
+
+
+
+
Пользователи
+
+
+
+ {users &&
+ users.map((user) => (
+
+
+

+
+
+
{user.name}
+
{user.username}
+
Роль: {user.role}
+
+ Связанные ЖК:{" "}
+ {builds
+ ?.filter((build) => user.buildIds?.includes(build.id))
+ .map((build) => build.name)
+ .join(", ") || "Нет связанных сборок"}
+
+
+
+
+
}
+ onlyIcon
+ handleClick={() =>
+ companyId &&
+ setModal(
+
+ )
+ }
+ />
+
+ ))}
+
+
+
+
+
+
+ {(state) => (
+
+
+
+ )}
+
+
+ );
+}
+
+export default AdminCompanyPage;
diff --git a/client/src/pages/AdminPage.tsx b/client/src/pages/AdminPage.tsx
new file mode 100644
index 0000000..08f3ce6
--- /dev/null
+++ b/client/src/pages/AdminPage.tsx
@@ -0,0 +1,90 @@
+import { useEffect, useState } from "react";
+import ICompany from "../types/ICompany";
+import api from "../utils/api";
+import IError from "../types/IError";
+import Button from "../components/Button";
+import useModalStore from "../stores/useModalStore";
+import CreateCompanyModal from "../components/modals/CreateCompanyModal";
+import ModalContainer from "../components/ModalContainer";
+import { useNavigate } from "react-router-dom";
+import SpinnerIcon from "../components/icons/SpinnerIcon";
+import { Transition } from "react-transition-group";
+
+function AdminPage() {
+ const [companies, setCompanies] = useState();
+ const { setModal } = useModalStore();
+ const navigate = useNavigate();
+ const [isLoading, setIsLoading] = useState(true);
+
+ async function getCompanies() {
+ try {
+ const result: ICompany[] | IError = await api
+ .get(`admin/companies`)
+ .json();
+
+ if ("error" in result) {
+ alert(result.error);
+ return;
+ }
+
+ setCompanies(result);
+ } catch (error) {
+ alert((error as Error).message);
+ }
+ }
+
+ useEffect(() => {
+ getCompanies();
+ }, []);
+
+ useEffect(() => {
+ if (!companies) {
+ return;
+ }
+
+ setIsLoading(false);
+ }, [companies]);
+
+ return (
+
+
+
Компании
+
+
+
+ {companies &&
+ companies.map((company) => (
+
+ ))}
+
+
+
+
+
+ {(state) => (
+
+
+
+ )}
+
+
+ );
+}
+
+export default AdminPage;
diff --git a/client/src/pages/DashboardPage.tsx b/client/src/pages/DashboardPage.tsx
index dac4886..9f57945 100644
--- a/client/src/pages/DashboardPage.tsx
+++ b/client/src/pages/DashboardPage.tsx
@@ -27,6 +27,8 @@ import SpinnerIcon from "../components/icons/SpinnerIcon";
import ModalContainer from "../components/ModalContainer";
import ChevronLeftIcon from "../components/icons/ChevronLeftIcon";
import ChevronRightIcon from "../components/icons/ChevronRightIcon";
+import IUser from "../types/IUser";
+import IError from "../types/IError";
function DashboardPage() {
const { user } = useAuthStore();
@@ -69,7 +71,7 @@ function DashboardPage() {
}
function findSessionsByTime(time: Date) {
- if (!scheduledSessions) return [];
+ if (!scheduledSessions || !scheduledSessions.length) return [];
const foundScheduledSessions = scheduledSessions?.filter(
(scheduledSession) => isEqual(new Date(scheduledSession.startAt), time)
@@ -180,14 +182,20 @@ function DashboardPage() {
}
try {
- const result: any = await api
- .get(`companies/${company.id}/builds/${selectedBuild.id}/users`)
+ const result: IUser[] | IError = await api
+ .get(`users?companyId=${company.id}&buildIds=${selectedBuild.id}`)
.json();
+
+ if ("error" in result) {
+ alert(result.error);
+ return;
+ }
+
+ console.log("result", result);
+
setManagers(result);
} catch (error) {
- if (error instanceof Error) {
- console.log("Error: ", error.message);
- }
+ alert((error as Error).message);
}
}
diff --git a/client/src/types/IError.ts b/client/src/types/IError.ts
new file mode 100644
index 0000000..02779e8
--- /dev/null
+++ b/client/src/types/IError.ts
@@ -0,0 +1,5 @@
+interface IError {
+ error: string;
+}
+
+export default IError;
diff --git a/client/src/types/IUser.ts b/client/src/types/IUser.ts
index ecff65a..52b9965 100644
--- a/client/src/types/IUser.ts
+++ b/client/src/types/IUser.ts
@@ -1,10 +1,11 @@
interface IUser {
id: string;
companyId: string;
- avatar: string;
+ avatar?: string;
name: string;
username: string;
role: string;
+ buildIds?: string[];
}
export default IUser;
diff --git a/server/ecosystem.config.js b/server/ecosystem.config.cjs
similarity index 100%
rename from server/ecosystem.config.js
rename to server/ecosystem.config.cjs
diff --git a/server/package.json b/server/package.json
index 992f312..23c8b8b 100644
--- a/server/package.json
+++ b/server/package.json
@@ -2,10 +2,11 @@
"name": "server",
"private": true,
"version": "0.0.0",
+ "type": "module",
"scripts": {
- "dev": "nodemon src/index.ts",
- "build": "npx tsc",
- "start": "node dist/index.js"
+ "dev": "nodemon --exec node --import=./register.js ./src/index.ts",
+ "build": "npx tsc -p ./",
+ "start": "node ./dist/index.js"
},
"dependencies": {
"bcrypt": "^5.1.1",
diff --git a/server/register.js b/server/register.js
new file mode 100644
index 0000000..2134bb0
--- /dev/null
+++ b/server/register.js
@@ -0,0 +1,4 @@
+import { register } from "module";
+import { pathToFileURL } from "url";
+
+register("ts-node/esm", pathToFileURL("./"));
diff --git a/server/src/index.ts b/server/src/index.ts
index 34ce590..714f9f0 100644
--- a/server/src/index.ts
+++ b/server/src/index.ts
@@ -1,22 +1,25 @@
import "dotenv/config";
import express, { json } from "express";
-import connectDB from "./config/db";
+import connectDB from "./config/db.js";
import cors from "cors";
-import loginRouter from "./routes/login";
-import registrationRouter from "./routes/registration";
-import authMiddleware from "./middlewares/auth";
-import companiesRouter from "./routes/companies";
-import usersRouter from "./routes/users";
-import buildsRouter from "./routes/builds";
-import actionsRouter from "./routes/actions";
-import schedulesRouter from "./routes/schedules";
-import scheduledSessionsRoute from "./routes/scheduledSessions";
+import loginRouter from "./routes/login.js";
+import registrationRouter from "./routes/registration.js";
+import authMiddleware from "./middlewares/auth.js";
+import companiesRouter from "./routes/companies.js";
+import usersRouter from "./routes/users.js";
+import buildsRouter from "./routes/builds.js";
+import actionsRouter from "./routes/actions.js";
+import schedulesRouter from "./routes/schedules.js";
+import scheduledSessionsRoute from "./routes/scheduledSessions.js";
+import adminCompaniesRoute from "./routes/admin/adminCompaniesRoute.js";
+import adminBuildsRoute from "./routes/admin/adminBuildsRoute.js";
+import adminUsersRoute from "./routes/admin/adminUsersRoute.js";
+
+await connectDB();
const app = express();
const port = process.env.PORT || 3000;
-connectDB();
-
app.use(json());
app.use(cors({ origin: "*" }));
@@ -28,6 +31,9 @@ app.use("/scheduled_sessions", scheduledSessionsRoute);
app.use("/schedules", schedulesRouter);
app.use("/companies", authMiddleware, companiesRouter);
app.use("/users", authMiddleware, usersRouter);
+app.use("/admin/companies", adminCompaniesRoute);
+app.use("/admin/builds", adminBuildsRoute);
+app.use("/admin/users", adminUsersRoute);
app.listen(port, () => {
console.log(`Server listening on port ${port}`);
diff --git a/server/src/middlewares/auth.ts b/server/src/middlewares/auth.ts
index 7f2398d..bbfdb18 100644
--- a/server/src/middlewares/auth.ts
+++ b/server/src/middlewares/auth.ts
@@ -1,7 +1,7 @@
import { Request, Response, NextFunction } from "express";
import jwt, { Secret } from "jsonwebtoken";
-import Token from "../models/Token";
-import User from "../models/User";
+import Token from "../models/Token.js";
+import User from "../models/User.js";
async function authMiddleware(req: Request, res: Response, next: NextFunction) {
if (!req.headers.authorization || !req.headers.authorization.split(" ")[1]) {
diff --git a/server/src/models/BuildUser.ts b/server/src/models/BuildUser.ts
deleted file mode 100644
index 04ff551..0000000
--- a/server/src/models/BuildUser.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { model, Schema } from "mongoose";
-
-const buildUserSchema = new Schema(
- {
- buildId: {
- type: Schema.Types.ObjectId,
- ref: "Build",
- required: true,
- },
- userId: {
- type: Schema.Types.ObjectId,
- ref: "User",
- required: true,
- },
- },
- {
- timestamps: true,
- toJSON: { virtuals: true },
- toObject: { virtuals: true },
- }
-);
-
-const BuildUser = model("Build_User", buildUserSchema);
-
-export default BuildUser;
diff --git a/server/src/models/User.ts b/server/src/models/User.ts
index d514d52..9d677be 100644
--- a/server/src/models/User.ts
+++ b/server/src/models/User.ts
@@ -11,22 +11,26 @@ const userSchema = new Schema(
type: String,
required: true,
},
- companyId: {
- type: Schema.Types.ObjectId,
- ref: "Company",
+ role: {
+ type: String,
required: true,
},
name: {
type: String,
required: true,
},
- role: {
- type: String,
- required: true,
- },
avatar: {
type: String,
},
+ companyId: {
+ type: Schema.Types.ObjectId,
+ ref: "Company",
+ required: true,
+ },
+ buildIds: {
+ type: [Schema.Types.ObjectId],
+ ref: "Build",
+ },
},
{
timestamps: true,
diff --git a/server/src/routes/actions.ts b/server/src/routes/actions.ts
index 540612b..9a5e527 100644
--- a/server/src/routes/actions.ts
+++ b/server/src/routes/actions.ts
@@ -1,5 +1,5 @@
import { Router } from "express";
-import Action from "../models/Action";
+import Action from "../models/Action.js";
const actionsRouter = Router();
diff --git a/server/src/routes/admin/adminBuildsRoute.ts b/server/src/routes/admin/adminBuildsRoute.ts
new file mode 100644
index 0000000..d5d886c
--- /dev/null
+++ b/server/src/routes/admin/adminBuildsRoute.ts
@@ -0,0 +1,60 @@
+import { Router } from "express";
+import Build from "../../models/Build.js";
+
+const router = Router();
+
+router.get("/", async (req, res) => {
+ try {
+ const result = await Build.find(req.query);
+
+ res.json(result);
+ } catch (error) {
+ res.json({ error: (error as Error).message });
+ }
+});
+
+router.get("/:id", async (req, res) => {
+ try {
+ const result = await Build.findById(req.params.id);
+
+ res.json(result);
+ } catch (error) {
+ res.json({ error: (error as Error).message });
+ }
+});
+
+router.post("/", async (req, res) => {
+ try {
+ const result = await Build.create(req.body);
+
+ res.json(result);
+ } catch (error) {
+ res.json({ error: (error as Error).message });
+ }
+});
+
+router.put("/:id", async (req, res) => {
+ try {
+ const result = await Build.findByIdAndUpdate(req.params.id, req.body, {
+ new: true,
+ });
+
+ res.json(result);
+ } catch (error) {
+ res.json({ error: (error as Error).message });
+ }
+});
+
+router.delete("/:id", async (req, res) => {
+ try {
+ const result = await Build.findByIdAndRemove(req.params.id);
+
+ res.json(result);
+ } catch (error) {
+ res.json({ error: (error as Error).message });
+ }
+});
+
+const adminBuildsRoute = router;
+
+export default adminBuildsRoute;
diff --git a/server/src/routes/admin/adminCompaniesRoute.ts b/server/src/routes/admin/adminCompaniesRoute.ts
new file mode 100644
index 0000000..b6a0208
--- /dev/null
+++ b/server/src/routes/admin/adminCompaniesRoute.ts
@@ -0,0 +1,60 @@
+import { Router } from "express";
+import Company from "../../models/Company.js";
+
+const router = Router();
+
+router.get("/", async (req, res) => {
+ try {
+ const result = await Company.find(req.query);
+
+ res.json(result);
+ } catch (error) {
+ res.json({ error: (error as Error).message });
+ }
+});
+
+router.get("/:id", async (req, res) => {
+ try {
+ const result = await Company.findById(req.params.id);
+
+ res.json(result);
+ } catch (error) {
+ res.json({ error: (error as Error).message });
+ }
+});
+
+router.post("/", async (req, res) => {
+ try {
+ const result = await Company.create(req.body);
+
+ res.json(result);
+ } catch (error) {
+ res.json({ error: (error as Error).message });
+ }
+});
+
+router.put("/:id", async (req, res) => {
+ try {
+ const result = await Company.findByIdAndUpdate(req.params.id, req.body, {
+ new: true,
+ });
+
+ res.json(result);
+ } catch (error) {
+ res.json({ error: (error as Error).message });
+ }
+});
+
+router.delete("/:id", async (req, res) => {
+ try {
+ const result = await Company.findByIdAndRemove(req.params.id);
+
+ res.json(result);
+ } catch (error) {
+ res.json({ error: (error as Error).message });
+ }
+});
+
+const adminCompaniesRoute = router;
+
+export default adminCompaniesRoute;
diff --git a/server/src/routes/admin/adminUsersRoute.ts b/server/src/routes/admin/adminUsersRoute.ts
new file mode 100644
index 0000000..cca78e2
--- /dev/null
+++ b/server/src/routes/admin/adminUsersRoute.ts
@@ -0,0 +1,62 @@
+import bcrypt from "bcrypt";
+import { Router } from "express";
+import User from "../../models/User.js";
+
+const router = Router();
+
+router.get("/", async (req, res) => {
+ try {
+ const result = await User.find(req.query);
+
+ res.json(result);
+ } catch (error) {
+ res.json({ error: (error as Error).message });
+ }
+});
+
+router.get("/:id", async (req, res) => {
+ try {
+ const result = await User.findById(req.params.id);
+
+ res.json(result);
+ } catch (error) {
+ res.json({ error: (error as Error).message });
+ }
+});
+
+router.post("/", async (req, res) => {
+ try {
+ const passwordHash = bcrypt.hashSync(req.body.password, 12);
+ const result = await User.create({ ...req.body, password: passwordHash });
+
+ res.json(result);
+ } catch (error) {
+ res.json({ error: (error as Error).message });
+ }
+});
+
+router.put("/:id", async (req, res) => {
+ try {
+ const result = await User.findByIdAndUpdate(req.params.id, req.body, {
+ new: true,
+ });
+
+ res.json(result);
+ } catch (error) {
+ res.json({ error: (error as Error).message });
+ }
+});
+
+router.delete("/:id", async (req, res) => {
+ try {
+ const result = await User.findByIdAndRemove(req.params.id);
+
+ res.json(result);
+ } catch (error) {
+ res.json({ error: (error as Error).message });
+ }
+});
+
+const adminUsersRoute = router;
+
+export default adminUsersRoute;
diff --git a/server/src/routes/builds.ts b/server/src/routes/builds.ts
index 5a300c4..cfa0631 100644
--- a/server/src/routes/builds.ts
+++ b/server/src/routes/builds.ts
@@ -1,5 +1,5 @@
import { Router } from "express";
-import Build from "../models/Build";
+import Build from "../models/Build.js";
const buildsRouter = Router();
diff --git a/server/src/routes/companies.ts b/server/src/routes/companies.ts
index 92fefea..22497e1 100644
--- a/server/src/routes/companies.ts
+++ b/server/src/routes/companies.ts
@@ -1,10 +1,9 @@
import { Router } from "express";
-import Company from "../models/Company";
import { parseISO, startOfDay, endOfDay } from "date-fns";
-import ScheduledSession from "../models/ScheduledSession";
-import Schedule from "../models/Schedule";
-import BuildUser from "../models/BuildUser";
-import User from "../models/User";
+import Company from "../models/Company.js";
+import ScheduledSession from "../models/ScheduledSession.js";
+import Schedule from "../models/Schedule.js";
+import User from "../models/User.js";
const router = Router();
@@ -55,8 +54,8 @@ router.get("/:id/builds/:buildId/users", async (req, res) => {
return;
}
- const buildUsers: any = await BuildUser.find({
- buildId: req.params.buildId,
+ const buildUsers: any = await User.find({
+ buildIds: req.params.buildId,
});
const users = [];
@@ -195,10 +194,8 @@ router.get(
return;
}
- const buildUsers = await BuildUser.find({ buildId: req.params.buildId });
- let buildUserIds = buildUsers.map((buildUser) =>
- buildUser.userId.toString()
- );
+ const buildUsers = await User.find({ buildIds: req.params.buildId });
+ let buildUserIds = buildUsers.map((buildUser) => buildUser.toString());
const scheduledSessionsAtTime = await ScheduledSession.find({
buildId: req.params.buildId,
diff --git a/server/src/routes/login.ts b/server/src/routes/login.ts
index 58d0b87..21ed136 100644
--- a/server/src/routes/login.ts
+++ b/server/src/routes/login.ts
@@ -1,8 +1,8 @@
import { Router } from "express";
import jwt, { Secret } from "jsonwebtoken";
import bcrypt from "bcrypt";
-import User from "../models/User";
-import Token from "../models/Token";
+import User from "../models/User.js";
+import Token from "../models/Token.js";
const router = Router();
diff --git a/server/src/routes/registration.ts b/server/src/routes/registration.ts
index 0ea8f84..de33828 100644
--- a/server/src/routes/registration.ts
+++ b/server/src/routes/registration.ts
@@ -1,8 +1,8 @@
import { Router } from "express";
import jwt, { Secret } from "jsonwebtoken";
import bcrypt from "bcrypt";
-import User from "../models/User";
-import Token from "../models/Token";
+import User from "../models/User.js";
+import Token from "../models/Token.js";
const router = Router();
diff --git a/server/src/routes/scheduledSessions.ts b/server/src/routes/scheduledSessions.ts
index 191e897..31b2afe 100644
--- a/server/src/routes/scheduledSessions.ts
+++ b/server/src/routes/scheduledSessions.ts
@@ -1,7 +1,7 @@
import { Router } from "express";
-import ScheduledSession from "../models/ScheduledSession";
-import Build from "../models/Build";
-import Schedule from "../models/Schedule";
+import ScheduledSession from "../models/ScheduledSession.js";
+import Build from "../models/Build.js";
+import Schedule from "../models/Schedule.js";
import {
addMinutes,
areIntervalsOverlapping,
diff --git a/server/src/routes/schedules.ts b/server/src/routes/schedules.ts
index dc8e61e..2b56676 100644
--- a/server/src/routes/schedules.ts
+++ b/server/src/routes/schedules.ts
@@ -1,5 +1,5 @@
import { Router } from "express";
-import Schedule from "../models/Schedule";
+import Schedule from "../models/Schedule.js";
const schedulesRouter = Router();
diff --git a/server/src/routes/users.ts b/server/src/routes/users.ts
index e25cf71..3a62862 100644
--- a/server/src/routes/users.ts
+++ b/server/src/routes/users.ts
@@ -1,21 +1,28 @@
import { Router } from "express";
+import User from "../models/User.js";
// import User from "../models/User.js";
const router = Router();
-// usersRouter.get("/", async (_req, res) => {
-// const users = await User.find();
-// console.log(users);
+router.get("/", async (req, res) => {
+ try {
+ const result = await User.find(req.query);
-// res.json(users);
-// });
+ res.json(result);
+ } catch (error) {
+ res.json({ error: (error as Error).message });
+ }
+});
-// usersRouter.get("/:id", async (req, res) => {
-// const user = await User.findById(req.params.id);
-// console.log(user);
+router.get("/:id", async (req, res) => {
+ try {
+ const result = await User.findById(req.params);
-// res.json(user);
-// });
+ res.json(result);
+ } catch (error) {
+ res.json({ error: (error as Error).message });
+ }
+});
const usersRouter = router;
diff --git a/server/tsconfig.json b/server/tsconfig.json
index 2ca8d8c..19bdf09 100644
--- a/server/tsconfig.json
+++ b/server/tsconfig.json
@@ -1,18 +1,109 @@
{
"compilerOptions": {
- "target": "ESNext",
- "module": "CommonJS",
- "moduleResolution": "Node",
- "outDir": "./dist",
- "esModuleInterop": true,
- "forceConsistentCasingInFileNames": true,
- "strict": true,
- "noUnusedLocals": true,
- "noUnusedParameters": true,
- "noFallthroughCasesInSwitch": true,
- "skipLibCheck": true,
- },
- "ts-node": {
- "transpileOnly": true
+ /* Visit https://aka.ms/tsconfig to read more about this file */
+
+ /* Projects */
+ // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
+ // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
+ // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
+ // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
+ // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
+ // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
+
+ /* Language and Environment */
+ "target": "ESNext" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
+ // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
+ // "jsx": "preserve", /* Specify what JSX code is generated. */
+ // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
+ // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
+ // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
+ // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
+ // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
+ // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
+ // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
+ // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
+ // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
+
+ /* Modules */
+ "module": "NodeNext" /* Specify what module code is generated. */,
+ // "rootDir": "./", /* Specify the root folder within your source files. */
+ "moduleResolution": "NodeNext" /* Specify how TypeScript looks up a file from a given module specifier. */,
+ // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
+ // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
+ // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
+ // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
+ // "types": [], /* Specify type package names to be included without being referenced in a source file. */
+ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
+ // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
+ // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
+ // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
+ // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
+ // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
+ "resolveJsonModule": true /* Enable importing .json files. */,
+ // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
+ // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */
+
+ /* JavaScript Support */
+ // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
+ // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
+ // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
+
+ /* Emit */
+ // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
+ // "declarationMap": true, /* Create sourcemaps for d.ts files. */
+ // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
+ // "sourceMap": true, /* Create source map files for emitted JavaScript files. */
+ // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
+ // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
+ "outDir": "./dist" /* Specify an output folder for all emitted files. */,
+ // "removeComments": true, /* Disable emitting comments. */
+ // "noEmit": true, /* Disable emitting files from a compilation. */
+ // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
+ // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
+ // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
+ // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
+ // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
+ // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
+ // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
+ // "newLine": "crlf", /* Set the newline character for emitting files. */
+ // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
+ // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
+ // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
+ // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
+ // "declarationDir": "./", /* Specify the output directory for generated declaration files. */
+ // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
+
+ /* Interop Constraints */
+ // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
+ // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
+ // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
+ "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
+ // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
+ "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
+
+ /* Type Checking */
+ "strict": true /* Enable all strict type-checking options. */,
+ // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
+ // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
+ // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
+ // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
+ // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
+ // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
+ // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
+ // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
+ // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
+ // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
+ // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
+ // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
+ // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
+ // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
+ // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
+ // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
+ // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
+ // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
+
+ /* Completeness */
+ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
+ "skipLibCheck": true /* Skip type checking all .d.ts files. */
}
}