diff --git a/api/controllers/profileController.js b/api/controllers/profileController.js index 8fefa16..a34d115 100644 --- a/api/controllers/profileController.js +++ b/api/controllers/profileController.js @@ -6,6 +6,7 @@ const Profile = require("../models/profileModel"); module.exports = { createProfile: async ({ + admin, firstName, lastName, email, @@ -14,6 +15,7 @@ module.exports = { workorders, }) => { const newProfile = new Profile({ + admin, firstName, lastName, phone, @@ -29,7 +31,6 @@ module.exports = { const profileRes = await Profile.findOne({ email }) .populate("workorders") .exec(); - console.log(`"profileRes.workorders": ${profileRes.workorders}`); return profileRes; } catch (err) { throw err; @@ -37,12 +38,11 @@ module.exports = { }, findProfileById: async (id) => { try { - const profile = await Profile.findById(id) - .populate("workorders") - .exec(); + const profile = await Profile.findById(id).populate("workorders").exec(); //* return everything but password return { id: profile._id, + admin: profile.admin, firstName: profile.firstName, lastName: profile.lastName, email: profile.email, @@ -52,4 +52,22 @@ module.exports = { throw err; } }, + findProfileByAny: async (inputValue) => { + try { + const profileRes = await Profile.find({ + $or: [ + { firstName: inputValue }, + { lastName: inputValue }, + { email: inputValue }, + // TODO - mongoose.js CastError: Cast to number failed for value + // { phone: inputValue }, + ], + }) + .populate("workorders") + .exec(); + return profileRes; + } catch (err) { + throw err; + } + }, }; diff --git a/api/controllers/serviceController.js b/api/controllers/serviceController.js new file mode 100644 index 0000000..40472b0 --- /dev/null +++ b/api/controllers/serviceController.js @@ -0,0 +1,25 @@ +const Services = require("../models/serviceSchema"); + +exports.findService = async (title) => { + try { + const serviceRes = await Services.find({ title }); + return serviceRes; + } catch (err) { + throw err; + } +}; + +exports.createService = async ({ serviceType, title, desc, price }) => { + try { + const newService = new Service({ + serviceType, + title, + desc, + price, + }); + const service = await newService.save(); + return service; + } catch (err) { + console.log(`error: ${err}`); + } +}; diff --git a/api/controllers/workorderController.js b/api/controllers/workorderController.js index a803e8e..4c5162d 100644 --- a/api/controllers/workorderController.js +++ b/api/controllers/workorderController.js @@ -1,5 +1,4 @@ const Workorder = require("../models/workorderModel"); -// const { findProfileById } = require("./profileController"); const Profile = require("../models/profileModel"); exports.createWorkorder = async ({ userId, brand, model, colour }) => { diff --git a/api/models/profileModel.js b/api/models/profileModel.js index 5554b05..2b027c9 100644 --- a/api/models/profileModel.js +++ b/api/models/profileModel.js @@ -9,6 +9,11 @@ const { Schema } = mongoose; const profileSchema = new Schema( { + admin: { + type: Boolean, + required: true, + default: false, + }, firstName: { type: String, required: true, @@ -20,20 +25,28 @@ const profileSchema = new Schema( phone: { type: Number, required: true, + minlength: 10, + maxlength: 11, + match: /^\D?(\d{3})\D?\D?(\d{3})\D?(\d{4})$/, }, email: { type: String, - required: true, + required: true, unique: true, + match: /^.+@.+$/, }, password: { type: String, required: true, + minlength: 8, + match: /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*])(?=.{8,})/, }, - workorders: [{ - type: Schema.Types.ObjectId, - ref: 'Workorder' - }] + workorders: [ + { + type: Schema.Types.ObjectId, + ref: "Workorder", + }, + ], }, { timestamps: { createdAt: "created_at", updatedAt: "updated_at" }, diff --git a/api/models/serviceSchema.js b/api/models/serviceSchema.js index 4598b63..e021a59 100644 --- a/api/models/serviceSchema.js +++ b/api/models/serviceSchema.js @@ -2,10 +2,28 @@ const mongoose = require("mongoose"); const { Schema } = mongoose; //___ servicesSchema -// service: String +// serviceType: String +// title: String +// description: [String] +// price: Number const serviceSchema = new Schema({ - services: String, + serviceType: { + type: String, + required: true, + }, + title: { + type: String, + required: true, + }, + desc: { + type: [String], + required: true, + }, + price: { + type: Number, + required: true, + }, }); -module.exports = serviceSchema; +module.exports = mongoose.model("Services", serviceSchema); diff --git a/api/routes/profileRoutes.js b/api/routes/profileRoutes.js index f126680..49177af 100644 --- a/api/routes/profileRoutes.js +++ b/api/routes/profileRoutes.js @@ -3,12 +3,12 @@ const { createProfile, findProfileByEmail, findProfileById, + findProfileByAny, } = require("../controllers/profileController"); const { verifyToken } = require("../middleware/verifyToken"); const { createToken } = require("../tokens/tokenService"); const router = express.Router(); -// router.get("/search/email/:email", findProfileByEmail); router.route("/create").post(async (req, res) => { const { firstName, lastName, email, phone, password } = req.body; @@ -54,7 +54,6 @@ router.route("/create").post(async (req, res) => { }); router.route("/login").post(async (req, res) => { - console.log("route: login"); const { email, password } = req.body; if (!email || email === " ") { res.status(400).json({ message: "email must be provided" }); @@ -87,13 +86,34 @@ router.route("/login").post(async (req, res) => { } }); -// where workorder route used to be +router +.route("/search/:searchValue") +.get(async (req, res) => { + const { searchValue } = req.params; + if (!searchValue || searchValue === "") { + res.status(400).json({ message: "nothing to search" }); + return; + } else { + try { + const match = await findProfileByAny(searchValue); + if (!match) { + res + .status(400) + .json({ message: `Search for "${searchValue}" returned no results` }); + return; + } + res.status(200).json(match); + } catch (err) { + console.log(err); + res.status(500).json({ message: "Internal server error" }); + } + } +}); router .use(verifyToken) .route("/this-profile") .get(async (req, res) => { - console.log(`/this-profile: ${req.profile.id}`); try { const profile = await findProfileById(req.profile.id); res.json({ data: profile }); diff --git a/api/routes/serviceRoutes.js b/api/routes/serviceRoutes.js new file mode 100644 index 0000000..c08cf68 --- /dev/null +++ b/api/routes/serviceRoutes.js @@ -0,0 +1,52 @@ +const express = require("express"); +const router = express.Router(); +const Services = require("../models/serviceSchema"); + +router.route("/").get(async (req, res) => { + try { + const services = await Services.find(); + res.json(services); + } catch (err) { + res.json({ error: err }); + } +}); + +router.route("/create").post(async (req, res) => { + const { serviceType, title, desc, price } = req.body; + if (!serviceType || serviceType === " ") { + res.status(400).json({ message: "service type must be provided" }); + return; + } + if (!title || title === " ") { + res.status(400).json({ message: "title must be provided" }); + return; + } + if (!desc || desc === " ") { + res.status(400).json({ message: "description must be provided" }); + return; + } + if (!price || price === " ") { + res.status(400).json({ message: "price must be provided" }); + return; + } + try { + const service = await findService(title); + if (service) { + res + .status(400) + .json({ message: `a service with this title already exists` }); + } + const newService = await createService({ + serviceType, + title, + desc, + price, + }); + res.status(200).json({ data: { id: newService._id } }); + } catch (err) { + console.log(err); + res.status(500).json({ message: "Internal server error" }); + } +}); + +module.exports = router; diff --git a/api/routes/workorderRoutes.js b/api/routes/workorderRoutes.js index 5fe65a3..2931a8d 100644 --- a/api/routes/workorderRoutes.js +++ b/api/routes/workorderRoutes.js @@ -2,9 +2,9 @@ const express = require("express"); const router = express.Router(); const Workorder = require("../models/workorderModel"); const { verifyToken } = require("../middleware/verifyToken"); -const { createToken } = require("../tokens/tokenService"); -const { findProfileById } = require("../controllers/profileController"); const { createWorkorder } = require("../controllers/workorderController"); +// const { createToken } = require("../tokens/tokenService"); +// const { findProfileById } = require("../controllers/profileController"); // TODO // @route GET/workorders @@ -26,7 +26,7 @@ router.get("/", async (req, res) => { // router.post("/create/:userId", async (req, res) => { router - // .use(verifyToken) + .use(verifyToken) .route("/create/:userId") .post(async (req, res) => { const { brand, model, colour } = req.body; diff --git a/package-lock.json b/package-lock.json index 7be68b2..24869e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9691,6 +9691,14 @@ } } }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "requires": { + "loose-envify": "^1.0.0" + } + }, "ip": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", @@ -16617,6 +16625,15 @@ "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.8.tgz", "integrity": "sha512-HvPuUQnLp5H7TouGq3kzBeioJmXms1wHy9EGjz2OURWBp4qZO6AfGEcnxts1D/CbwPLRAgTMPCEgYhA3sEM4vw==" }, + "react-input-mask": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/react-input-mask/-/react-input-mask-2.0.4.tgz", + "integrity": "sha512-1hwzMr/aO9tXfiroiVCx5EtKohKwLk/NT8QlJXHQ4N+yJJFyUuMT+zfTpLBwX/lK3PkuMlievIffncpMZ3HGRQ==", + "requires": { + "invariant": "^2.2.4", + "warning": "^4.0.2" + } + }, "react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -19748,6 +19765,14 @@ "makeerror": "1.0.x" } }, + "warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "requires": { + "loose-envify": "^1.0.0" + } + }, "watchpack": { "version": "1.7.5", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz", diff --git a/package.json b/package.json index ac9af87..63fd5f9 100644 --- a/package.json +++ b/package.json @@ -21,10 +21,12 @@ "object-path": "^0.11.5", "react": "^16.13.1", "react-dom": "^16.13.1", + "react-input-mask": "^2.0.4", "react-router-dom": "^5.2.0", "react-scripts": "^4.0.1", "react-virtualized": "^9.22.3" }, + "proxy": "http://localhost:5000", "scripts": { "start:client": "react-scripts start", "start:server": "nodemon server.js --ignore src", @@ -35,7 +37,6 @@ "eslintConfig": { "extends": "react-app" }, - "proxy": "http://localhost:5000", "browserslist": { "production": [ ">0.2%", diff --git a/server.js b/server.js index 7208aec..31832d9 100644 --- a/server.js +++ b/server.js @@ -1,21 +1,22 @@ const express = require("express"); -const app = express(); const path = require("path"); require("dotenv/config"); const cors = require("cors"); const cookieParser = require("cookie-parser"); const mongoose = require("mongoose"); -// const DB_URI = process.env.DB_URI || "mongodb://localhost:27017/aero"; -const DB_URI = "mongodb://localhost:27017/aero"; -const PORT = 5000; +const DB_URI = process.env.DB_URI || "mongodb://localhost:27017/aero"; +const PORT = process.env.PORT || 5000; -app.use(cookieParser()); -app.use(express.json()); +const app = express(); app.use(express.urlencoded({ extended: true })); +app.use(express.json()); +app.use(cookieParser()); app.use( cors({ - origin: "http://localhost:5000", + origin: + "http://localhost:3000/" || + "https://aero-workorder-management.herokuapp.com", credentials: true, exposedHeaders: ["Set-Cookie"], allowedHeaders: ["Content-Type", "Authorization"], @@ -25,18 +26,22 @@ app.use( // * EXPRESS ROUTER MINI-APP const profile = require("./api/routes/profileRoutes"); const workorder = require("./api/routes/workorderRoutes"); +const services = require("./api/routes/serviceRoutes"); app.use("/api/profile", profile); app.use("/api/workorder", workorder); +app.use("/api/services", services); -if (process.env.NODE_ENV === 'production') { - app.use(express.static('./build')); +if (process.env.NODE_ENV === "production") { + app.use(express.static("./build")); // only add this part if you are using React Router - app.get('/*', (req,res) =>{ - console.log(path.join(__dirname+'/build/index.html')); - res.sendFile(path.join(__dirname+'/build/index.html')); + app.get("/*", (req, res) => { + console.log(path.join(__dirname + "/build/index.html")); + res.sendFile(path.join(__dirname + "/build/index.html")); }); } +app.use(express.static("/")); + mongoose .connect(DB_URI, { useNewUrlParser: true, @@ -44,14 +49,11 @@ mongoose useCreateIndex: true, }) .then(() => { + app.listen(PORT, function () { + console.log(`Server is now listening on port ${PORT}!`); + }); console.log(`Successfully connected to database server ${DB_URI}`); }) .catch((err) => { console.log({ error: err }); }); - -app.use(express.static("/")); - -app.listen(PORT, function () { - console.log(`Server is now listening on port ${PORT}!`); -}); diff --git a/src/App.css b/src/App.css index 74b5e05..1a8c21d 100644 --- a/src/App.css +++ b/src/App.css @@ -1,4 +1,4 @@ -.App { +/* .App { text-align: center; } @@ -35,4 +35,4 @@ to { transform: rotate(360deg); } -} +} */ diff --git a/src/App.js b/src/App.js index 8c48c6a..9326670 100644 --- a/src/App.js +++ b/src/App.js @@ -5,21 +5,54 @@ import MainContentSection from "./components/root/MainContentSection"; import Footer from "./components/root/Footer"; import { ImpersonatorProvider } from "./backend/authorization/ImpersonatorContext"; import { UserProvider } from "./backend/authorization/UserContext"; +import { makeStyles } from "@material-ui/core/styles"; + +const useStyles = makeStyles((theme) => ({ + header: { + display: "flex", + justifyContent: "space-between", + alignItems: "center", + height: "15vh", + // maxWidth: "900px", + padding: "2rem", + backgroundColor: "#212529", + }, + main: { + display: "flex", + flexDirection: "column", + justifyContent: "center", + // width: "90%", + // maxWidth: "1200px", + margin: "0 auto", + height: "70vh", + backgroundColor: "gray", + }, + footer: { + display: "flex", + flexDirection: "column", + justifyContent: "center", + alignItems: "center", + height: "15vh", + padding: "2rem", + backgroundColor: "#212529", + }, +})); const App = () => { + const classes = useStyles(); const [loginClick, setLoginClick] = useState(false); return ( -
+
-
+
-