├── .DS_Store ├── .gitignore ├── LICENSE ├── README.md ├── app.js ├── controllers ├── aboutController.js ├── authController.js ├── blogController.js └── dashboardController.js ├── middleware └── authMiddleware.js ├── models ├── .gitkeep ├── User.js └── blog.js ├── package-lock.json ├── package.json ├── public ├── .DS_Store ├── css │ ├── 404 │ │ └── 404.css │ ├── .gitkeep │ ├── about │ │ └── about.css │ ├── auth │ │ ├── login.css │ │ └── signup.css │ ├── base │ │ └── global.css │ ├── blogs │ │ └── blogs.css │ ├── contact │ │ └── contact.css │ ├── dashboard │ │ ├── blog-posts.css │ │ └── table.css │ ├── helpers │ │ └── variables.css │ └── layouts │ │ ├── dashboard-layout.css │ │ ├── main-layout.css │ │ └── user-profile.css ├── images │ └── dog.jpg ├── js │ ├── .gitkeep │ ├── blogs-create.js │ ├── delete-blog.js │ ├── login.js │ ├── main.js │ ├── signup.js │ └── users.js └── pictures │ ├── codingjourney.png │ ├── denoob.jpg │ ├── firstgame.jpg │ ├── kandie.jpg │ ├── kratos.jpg │ ├── murfy.jpg │ ├── ormzy.jpg │ ├── stephen.jpg │ └── traplocs.jpg ├── routes ├── .gitkeep ├── aboutRouter.js ├── authRouter.js ├── blogRouter.js └── dashboardRouter.js └── views ├── .DS_Store ├── 404.ejs ├── about └── about.ejs ├── auth ├── login.ejs └── signup.ejs ├── blog ├── blog.ejs └── blogs.ejs ├── contact.ejs ├── dashboard ├── .gitkeep ├── blogs-create.ejs ├── blogs.ejs ├── edit-blog.ejs ├── index.ejs ├── user-profile.ejs └── users.ejs ├── email-confirm.ejs ├── emails └── confirm.ejs ├── home.ejs ├── layouts ├── dashboard-layout.ejs └── main-layout.ejs └── partials ├── .gitkeep ├── footer.ejs ├── header.ejs ├── section-left.ejs └── section-right.ejs /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifElseCode/projectBlog/05e5e946281826d8d9c359ba485a88081c443bb3/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | 3 | .env -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # projectBlog 2 | An if-else coding project. 3 | 4 | # How to Clone this Repository 5 | Follow these steps to get the code inside this repo onto your local working area (computer). You can also watch [this](https://www.youtube.com/watch?v=pbPE4Dodn3o) YouTube video which takes you through these six steps. 6 | 7 | ## 1. Navigate to Project Location on PC 8 | On your computer, find a sensible place to save the project code once you've cloned it. If you're unsure where to do this, create a folder titled "projects" on your desktop and open this folder. When you clone the remote repository, Git will create a folder inside your projects folder, so there is no need to create another folder for this. 9 | 10 | ## 2. Navigate to Project Location in Terminal 11 | In your terminal, make sure your "working directory" is pointing to the folder you created in step one. To check what your terminal is looking at, type `pwd` into your terminal and hit enter (this stands for print working directory). The last folder listed should be the folder you just created in step one. If it isn't, then you'll need to use terminal commands like `cd ` to go into a folder or `cd ..` to go up a directory. 12 | 13 | ## 3. Copy URL 14 | In GitHub, click the green "Code" button and copy the URL that is shown in the little box. This is the URL that refers to this remote repo. 15 | 16 | ## 4. Clone the Repo in Git 17 | In your terminal, type this command: `git clone `. The is the URL you copied from GitHub. Paste that into the command and then hit enter. Well done, you've cloned this repo! 18 | 19 | ## 5. Check that the Files Have Been Successfully Cloned 20 | In the folder you created (called projects, in the suggestion we gave), you should now see another folder that was previously not there, called projectBlog. Open this folder and compare what you see here with the files and folders you see listed in the remote repo on GitHib. 21 | 22 | ## 6. Summary 23 | You now have a local clone of the remote repo. Changing the code that you've cloned won't effect the remote repo unless you push to it. But be careful when doing this, as you should run any major changes by your team members before changing the remote repo. 24 | 25 | # How to Run projectBlog 26 | Follow these steps to ensure you have all the correct packages/modules installed. You can also watch [this video]() to see what to do in action. 27 | 28 | ## 1. Make Sure You Have Cloned projectBlog to Your Computer 29 | If you haven't yet cloned the projectBlog repo to your computer, follow the steps listed under "How to Clone This Repository". To make sure this has been done correctly, compare what you see in your projectBlog folder with what you see in this remote repo. 30 | 31 | ## 2. Open projectBlog in Your Text Editor 32 | Open the projectBlog folder in your text editor. This will allow you to see all the files and folders within projectBlog in your text editor's working area. 33 | 34 | ## 3. Navigate to projectBlog in Your Terminal 35 | In your terminal, make sure your "working directory" is pointing to the projectBlog folder, wherever you have placed this. This will be the same as step 2 in "How to Clone This Repoisitory". 36 | 37 | ## 4. Install NPM Packages 38 | In your terminal, type the command `npm install` and then hit enter/return. This will install all the modules/packages that this project depends on. You'll see a folder titled "node_modules" in your projectBlog folder if this has been done succesfully. There is no need for you to enter this folder or edit any files here. 39 | 40 | ## 5. Run the Project 41 | To ensure that everything installed correctly, type `npm run dev` in your terminal and hit enter. Your terminal will return "Server is running on http://localhost:3000". Then, open your browser and type "localhost:3000". This will navigate to the home page of projectBlog and means that your computer is hosting this app/site, which your browser is accessing locally. Any changes you then save will restart the server and reflect after you have refreshed your browser. 42 | 43 | ## 6. Make Changes to projectBlog 44 | To make sure that projectBlog is working as it should, make some changes to a file and save these. Refresh your browser and you will see the changes that you made. If this doesn't work, you may have to go back to step 4. 45 | 46 | # Git Workflow 47 | Note: everything within angled brackets (eg, ``) refers to text that you will change depending on the context. They serve as a placeholder. In this tutorial, the name **feature/myBranch** is a generic term we have given for the sake of this tutorial. You will want to check with your team what system you have in place for naming your branches. To see this workflow in action, watch [this video](). 48 | 49 | ## 1. Pull Master Branch 50 | To make sure you're working with the latest code, ensure you've got the latest code in the **master** branch. To do this, type `git pull origin master` while on the **master** branch. Only then should you create you own branch. 51 | 52 | ## 2. Create feature/myBranch 53 | After pulling the latest code, you'll want to branch off **master**. To do so, type `git checkout -b feature/myBranch`. That will create a branch titled "**feature/myBranch**" and will check it out for you. Now you're ready to make changes to the code without effecting the **master** branch. (Of course, you'll want to name your branch something more descriptive than "**feature/myBranch**". Speak to your team members about how best to do this). 54 | 55 | ## 3. Commit to feature/myBranch and *Push* 56 | Once you're finished coding and ready to submit, you'll need to *add* it to the staging area and then *commit* it to your repo before *pushing* it to GitHub. 57 | 58 | To *add* it to the staging area, you can either type `git add ` to add a single file, or you can type `git add .` to add every modified file to the staging area. We'll use this latter command (`git add .`) more often. 59 | 60 | Once you've added it to the staging area, you'll then make a *commit* to **feature/myBranch**. To do this, enter `git commit -m ""`. Make your commit message short but descriptive. Now your working tree is clean. 61 | 62 | Once you've done that, *push* **feature/myBranch** to GitHub by typing `git push origin feature/myBranch`. 63 | 64 | ## 4. Checkout and Pull Master 65 | Move from **feature/myBranch** and checkout the **master** branch by typing `git checkout master`. Once you're on the **master branch**, *pull* the latest master branch from GitHub by typing `git pull origin master`. Since you started working on **feature/myBranch**, there may have been some recent changes to the **master** branch. 66 | 67 | ## 5. Checkout feature/myBranch, Merge master Branch Into *eature/myBranch and *Push* feature/myBranch Again 68 | Now that you have the latest version of the **master branch**, you'll want to *merge* it into **feature/myBranch**, to see if there are any conflicts. 69 | 70 | *Checkout* your branch by typing `git checkout feature/myBranch`. 71 | 72 | Then, to *merge* the **master** branch into **feature/myBranch**, enter `git merge master`. This will ensure that **feature/myBranch** is up to date with the latest **master** branch and that it has your code included without any conflicts. 73 | 74 | Once you have done this, *push* **feature/myBranch** by entering `git push origin feature/myBranch`. 75 | 76 | ## 6. Open a Pull Request Between **feature/myBranch** and **master** 77 | In GitHub, find **feature/myBranch** and click "*Pull request*". This will open a page where you can *Open a pull request*. 78 | 79 | You can leave a comment to add a more lengthy description of what you will be adding with this pull request. Once you've clicked "*Create pull request*", you will be taken to a page that will show you that your pull request needs to be approved by someone else before the pull request is completed. This is important because the pull request will merge **feature/myBranch** into **master**, which we do not want to mess up! 80 | 81 | Once someone else has reviewed and approved your pull request, you (or the person who approved) can complete the pull request (see step 11). 82 | 83 | ## 7. Checkout staging, then *pull* staging 84 | Back in your terminal, *checkout* the **staging** branch by entering `git checkout staging`. Once you're on **staging**, *pull* the latest version of it by entering `git pull origin staging`. 85 | 86 | ## 8. Merge feature/myBranch into staging 87 | While on the **staging** branch, you'll want to merge **feature/myBranch** into **staging**. Do this by entering `git merge feature/myBranch`, and make sure there are no conflicts. 88 | 89 | ## 9. Push staging 90 | Now you'll want to *push* **staging** to GitHub so that others can see how **feature/myBranch** interacts with everything else, without the risk of upsetting the **master** branch. 91 | 92 | ## 10. If Overlords are Happy with staging, then Complete the Pull Request Between feature/myBranch and master 93 | Your peers can now pull **staging** to see how your new code interacts with everything else. If they are happy with this, then they will review and approve your pull request which was opened in step 6. 94 | 95 | ## 11. Complete Pull Request and Delete Branch 96 | Once your pull request has been approved, delete **feature/myBranch** in GitHub and on your PC. 97 | 98 | After the pull request, you'll find a button to delete **feature/myBranch**. 99 | 100 | Back in your terminal, to delete **feature/myBranch**, first make sure you are not currently checking out **feature/myBranch**. Then, enter `git branch -d feature/myBranch`. One final step to clean things up: enter `git fetch -p` to make sure you have deleted **feature/myBranch**. -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const app = express(); 3 | const PORT = 3000; 4 | const mongoose = require("mongoose"); 5 | require("dotenv/config"); 6 | const cookieParser = require("cookie-parser"); 7 | const authMiddleware = require("./middleware/authMiddleware"); 8 | const Blog = require("./models/blog"); 9 | const morgan = require('morgan') 10 | const expressLayouts = require("express-ejs-layouts"); 11 | const aboutRouter = require("./routes/aboutRouter"); 12 | const blogRouter = require("./routes/blogRouter"); 13 | const authRouter = require("./routes/authRouter"); 14 | const dashboardRouter = require("./routes/dashboardRouter"); 15 | 16 | // Middleware 17 | app.use(morgan('dev')) 18 | app.use(expressLayouts); 19 | app.use(express.static("public")); 20 | app.use(express.json()); 21 | app.use(cookieParser()); 22 | app.use(express.urlencoded({ extended: true })); 23 | 24 | // View Engines 25 | app.set("view engine", "ejs"); 26 | app.set("layout", "layouts/main-layout"); 27 | 28 | // Connect to Database 29 | mongoose.connect(process.env.DB_CONNECTION, { useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true, useFindAndModify: false }) 30 | .then(result => { 31 | console.log("Connected to Database"); 32 | app.listen(PORT, () => console.log(`Server is running on http://localhost:${PORT}`)); 33 | }) 34 | .catch(err => console.log(err)); 35 | 36 | // Routes 37 | app.get("*", authMiddleware.checkUser); 38 | 39 | // HOME 40 | app.get("/", async (req, res) => { 41 | const months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; 42 | try { 43 | const blogs = await Blog.find().sort({ date: -1 }); 44 | res.render("home", { blogs, months }); 45 | } 46 | catch (err) { 47 | console.log(err); 48 | } 49 | }); 50 | 51 | // AUTHENTICATION 52 | app.use(authRouter); 53 | 54 | // ABOUT 55 | app.use("/about", aboutRouter); 56 | 57 | // BLOGS 58 | app.use("/blogs", blogRouter); 59 | 60 | // CONTACT 61 | app.get("/contact", (req, res) => res.render("contact")); 62 | 63 | // DASHBOARD 64 | app.use("/dashboard", dashboardRouter); 65 | 66 | 67 | // 404 error 68 | app.use((req, res) => res.render("404")); -------------------------------------------------------------------------------- /controllers/aboutController.js: -------------------------------------------------------------------------------- 1 | about_get = (req, res) => res.render("about/about"); 2 | 3 | module.exports = { 4 | about_get 5 | } -------------------------------------------------------------------------------- /controllers/authController.js: -------------------------------------------------------------------------------- 1 | const User = require("../models/User"); 2 | const jwt = require("jsonwebtoken"); 3 | require("dotenv/config"); 4 | const nodemailer = require("nodemailer"); 5 | const ejs = require("ejs"); 6 | 7 | const handleErrors = (err) => { 8 | let errors = { username: "", email: "", password: "" }; 9 | if (err.message === "Incorrect email") { 10 | errors.email = "That email address is not registered" 11 | } 12 | if (err.message === "Incorrect password") { 13 | errors.password = "That password is incorrect"; 14 | } 15 | if (err.code === 11000) { 16 | errors.email = "That email already exists"; 17 | return errors; 18 | } 19 | if (err.message.includes("user validation failed")) { 20 | Object.values(err.errors).forEach(({ properties }) => { 21 | errors[properties.path] = properties.message; 22 | }); 23 | } 24 | return errors; 25 | } 26 | 27 | const maxAge = 24 * 60 * 60; 28 | const createToken = (id) => { 29 | return jwt.sign({ id }, process.env.JWT_SECRET, { 30 | expiresIn: maxAge 31 | }); 32 | } 33 | 34 | const signup_get = (req, res) => res.render("auth/signup"); 35 | 36 | const signup_post = async (req, res) => { 37 | const { username, email, password } = req.body; 38 | const secureNumber = () => { 39 | return Math.floor(Math.random() * 10000); 40 | } 41 | const uniqueNumber = secureNumber(); 42 | try { 43 | const user = await User.create({ username, email, password, uniqueNumber }); 44 | const transporter = nodemailer.createTransport({ 45 | host: "smtp.gmail.com", 46 | auth: { 47 | user: process.env.EMAIL_ADDRESS, 48 | pass: process.env.EMAIL_PASSWORD, 49 | } 50 | }); 51 | ejs.renderFile("./views/emails/confirm.ejs", { username, id: user.id, uniqueNumber }, (err, data) => { 52 | if (err) { 53 | console.log(err) 54 | } else { 55 | transporter.sendMail({ 56 | from: "iecauth@gmail.com", 57 | to: email, 58 | subject: `Email Confirmation for ${username}`, 59 | html: data 60 | }, (err, info) => { 61 | if (err) { 62 | console.log(err); 63 | } else { 64 | console.log(info); 65 | } 66 | }); 67 | } 68 | }); 69 | res.status(200).json({ redirect: `/signup/confirm/${email}` }); 70 | } 71 | catch (err) { 72 | const errors = handleErrors(err); 73 | res.status(400).json({ errors }); 74 | } 75 | }; 76 | 77 | const signup_confirm_get = (req, res) => { 78 | const email = req.params.id; 79 | res.render("email-confirm", { email }); 80 | } 81 | 82 | const signup_uniqueNumber_get = async (req, res) => { 83 | const uniqueNumber = req.params.uniqueNumber; 84 | try { 85 | const user = await User.findOne({ uniqueNumber }); 86 | if (user) { 87 | User.findByIdAndUpdate(user.id, { verified: true }) 88 | .then(result => { 89 | const token = createToken(user); 90 | res.cookie("jwt", token, { httpOnly: true, maxAge: maxAge * 1000 }); 91 | res.status(201).redirect("/"); 92 | }) 93 | .catch(err => console.log(err)); 94 | 95 | } else { 96 | res.send("USER NOT FOUND! Aaaaah!"); 97 | } 98 | } 99 | catch (err) { 100 | console.log(err); 101 | } 102 | } 103 | 104 | const login_get = (req, res) => res.render("auth/login"); 105 | 106 | const login_post = async (req, res) => { 107 | const { email, password } = req.body; 108 | try { 109 | const user = await User.login(email, password); 110 | const token = createToken(user._id); 111 | res.cookie("jwt", token, { httpOnly: true, maxAge: maxAge * 1000 }); 112 | res.status(200).json({ user: user._id }); 113 | } 114 | catch (err) { 115 | const errors = handleErrors(err); 116 | res.status(400).json({ errors }); 117 | } 118 | }; 119 | 120 | const logout_get = (req, res) => { 121 | res.cookie("jwt", "", { maxAge: 1 }); 122 | res.redirect("/"); 123 | } 124 | 125 | module.exports = { 126 | signup_get, 127 | signup_post, 128 | signup_confirm_get, 129 | signup_uniqueNumber_get, 130 | login_get, 131 | login_post, 132 | logout_get 133 | } 134 | -------------------------------------------------------------------------------- /controllers/blogController.js: -------------------------------------------------------------------------------- 1 | const Blog = require('../models/blog'); 2 | 3 | const blogs_get = async (req, res) => { 4 | const months = [ 5 | 'January', 6 | 'February', 7 | 'March', 8 | 'April', 9 | 'May', 10 | 'June', 11 | 'July', 12 | 'August', 13 | 'September', 14 | 'October', 15 | 'November', 16 | 'December', 17 | ]; 18 | 19 | try { 20 | const blogs = await Blog.find().sort({ date: -1 }); 21 | res.render('blog/blogs.ejs', { blogs, months }); 22 | } 23 | 24 | catch (err) { 25 | console.log(err); 26 | } 27 | 28 | } 29 | 30 | const blog_get = async (req, res) => { 31 | const id = req.params.id; 32 | const months = [ 33 | 'January', 34 | 'February', 35 | 'March', 36 | 'April', 37 | 'May', 38 | 'June', 39 | 'July', 40 | 'August', 41 | 'September', 42 | 'October', 43 | 'November', 44 | 'December', 45 | ]; 46 | try { 47 | const blog = await Blog.findById(id); 48 | res.render('blog/blog', { blog, months }); 49 | } 50 | catch (err) { 51 | console.log(err); 52 | } 53 | } 54 | 55 | const blog_like_patch = (req, res) => { 56 | 57 | const id = req.params.id; 58 | const email = req.body.email; 59 | 60 | Blog.findById(id) 61 | .then(blog => { 62 | blog.likes.push(email) 63 | blog.save() 64 | .then(result => res.status(200).json({ result })) 65 | .catch(err => console.log(err)); 66 | }); 67 | 68 | } 69 | 70 | module.exports = { 71 | blogs_get, 72 | blog_get, 73 | blog_like_patch 74 | } -------------------------------------------------------------------------------- /controllers/dashboardController.js: -------------------------------------------------------------------------------- 1 | const User = require("../models/User"); 2 | const Blog = require("../models/blog"); 3 | 4 | const dashboard_get = (req, res) => res.render("dashboard/index", { layout: "./layouts/dashboard-layout" }); 5 | 6 | const dashboard_users_get = async (req, res) => { 7 | try { 8 | const users = await User.find(); 9 | res.render("dashboard/users", { layout: "./layouts/dashboard-layout", users: users }); 10 | } 11 | catch (err) { 12 | res.status(400); 13 | console.log(err); 14 | } 15 | } 16 | 17 | const dashboard_users_delete = (req, res) => { 18 | const id = req.params.id; 19 | User.findByIdAndDelete(id) 20 | .then(result => res.json({ redirect: "/dashboard/users" })) 21 | .catch(err => console.log(err)); 22 | } 23 | 24 | const dashboard_users_admin_patch = (req, res) => { 25 | const id = req.params.id; 26 | User.findByIdAndUpdate(id, { role: { admin: true } }) 27 | .then(result => res.json({ redirect: "/dashboard/users" })) 28 | .catch(err => console.log(err)); 29 | } 30 | 31 | const dashboard_users_author_patch = (req, res) => { 32 | console.log("Something isn't happening?"); 33 | const id = req.params.id; 34 | User.findByIdAndUpdate(id, { role: { author: true } }) 35 | .then(result => res.json({ redirect: "/dashboard/users" })) 36 | .catch(err => console.log(err)); 37 | } 38 | 39 | const dashboard_blogs_get = async (req, res) => { 40 | const months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; 41 | try { 42 | const blogs = await Blog.find().sort({ date: -1 }); 43 | res.render("dashboard/blogs.ejs", { layout: "./layouts/dashboard-layout", blogs, months }); 44 | } 45 | catch (err) { 46 | console.log(err); 47 | } 48 | } 49 | 50 | const dashboard_blogs_create_get = (req, res) => res.render("dashboard/blogs-create", { layout: "./layouts/dashboard-layout" }); 51 | 52 | const dashboard_blogs_create_post = async (req, res) => { 53 | try { 54 | await Blog.create(req.body); 55 | } 56 | catch (err) { 57 | console.log(err); 58 | } 59 | } 60 | 61 | const dashboard_blogs_delete = async (req, res) => { 62 | Blog.findByIdAndDelete(req.params.id) 63 | .then(result => res.json()) 64 | .catch(err => console.log(err)); 65 | } 66 | 67 | 68 | const dashboard_blogs_edit = async (req, res) => { 69 | try { 70 | const id = req.params.id; 71 | const blogInfo = await Blog.findById(id); 72 | res.render("dashboard/edit-blog", { layout: "./layouts/dashboard-layout", blog: blogInfo }); 73 | } 74 | catch (err) { 75 | console.log(err); 76 | } 77 | } 78 | const dashboard_blogs_patch = async (req, res) => { 79 | const id = req.params.id; 80 | Blog.findByIdAndUpdate(id, { ...req.body.blog }) 81 | .then(result => res.redirect("/dashboard/blogs/")) 82 | .catch(err => console.log(err)); 83 | } 84 | 85 | module.exports = { 86 | dashboard_get, 87 | dashboard_users_get, 88 | dashboard_users_delete, 89 | dashboard_users_admin_patch, 90 | dashboard_users_author_patch, 91 | dashboard_blogs_get, 92 | dashboard_blogs_create_get, 93 | dashboard_blogs_create_post, 94 | dashboard_blogs_delete, 95 | dashboard_blogs_patch, 96 | dashboard_blogs_edit 97 | } -------------------------------------------------------------------------------- /middleware/authMiddleware.js: -------------------------------------------------------------------------------- 1 | const jwt = require("jsonwebtoken"); 2 | const User = require("../models/User"); 3 | require("dotenv/config"); 4 | 5 | // requireAuth could be used to protext routes if we need 6 | const requireAuth = (req, res, next) => { 7 | const token = req.cookies.jwt; 8 | if (token) { 9 | jwt.verify(token, process.env.JWT_SECRET, (err, decodedToken) => { 10 | next(); 11 | }); 12 | } 13 | } 14 | 15 | const checkUser = (req, res, next) => { 16 | const token = req.cookies.jwt; 17 | if (token) { 18 | jwt.verify(token, process.env.JWT_SECRET, async (err, decodedToken) => { 19 | if (err) { 20 | console.log(err); 21 | res.locals.user = null; 22 | next(); 23 | } else { 24 | let user = await User.findById(decodedToken.id); 25 | res.locals.user = user; 26 | next(); 27 | } 28 | }); 29 | } 30 | else { 31 | res.locals.user = null; 32 | next(); 33 | } 34 | } 35 | 36 | module.exports = { 37 | requireAuth, 38 | checkUser 39 | }; -------------------------------------------------------------------------------- /models/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifElseCode/projectBlog/05e5e946281826d8d9c359ba485a88081c443bb3/models/.gitkeep -------------------------------------------------------------------------------- /models/User.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | const Schema = mongoose.Schema; 3 | const { isEmail } = require("validator"); 4 | const bcrypt = require("bcrypt"); 5 | 6 | const userSchema = new Schema({ 7 | username: { 8 | type: String, 9 | required: [true, "Username is required"], 10 | unique: true, 11 | }, 12 | email: { 13 | type: String, 14 | requied: [true, "Email address required"], 15 | unique: true, 16 | lowercase: true, 17 | validate: [isEmail, "Please enter a valid email"] 18 | }, 19 | password: { 20 | type: String, 21 | required: [true, "Password required"], 22 | minlength: [8, "Minimum password length is 8 characters"] 23 | }, 24 | role: { 25 | admin: Boolean, 26 | author: Boolean, 27 | }, 28 | verified: { 29 | type: Boolean, 30 | default: false 31 | }, 32 | uniqueNumber: Number 33 | }); 34 | 35 | userSchema.pre("save", async function(next) { 36 | const salt = await bcrypt.genSalt(); 37 | this.password = await bcrypt.hash(this.password, salt); 38 | next(); 39 | }); 40 | 41 | userSchema.statics.login = async function(email, password) { 42 | const user = await this.findOne({ email }); 43 | if (user) { 44 | const auth = await bcrypt.compare(password, user.password); 45 | if (auth) { 46 | return user; 47 | } 48 | throw Error("Incorrect password"); 49 | } 50 | throw Error("Incorrect email"); 51 | } 52 | 53 | const User = mongoose.model("user", userSchema); 54 | 55 | module.exports = User; -------------------------------------------------------------------------------- /models/blog.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | const Schema = mongoose.Schema; 3 | 4 | const blogSchema = new Schema({ 5 | title: { 6 | type: String, 7 | required: true 8 | }, 9 | author: { 10 | type: String, 11 | required: true 12 | }, 13 | content: { 14 | type: String, 15 | required: true 16 | }, 17 | date: { 18 | type: Date, 19 | default: Date.now 20 | }, 21 | likes: Array 22 | }); 23 | 24 | module.exports = mongoose.model('Blog', blogSchema); 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "if-else-code", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "dependencies": { 7 | "bcrypt": "^5.0.0", 8 | "cookie-parser": "^1.4.5", 9 | "dotenv": "^8.2.0", 10 | "ejs": "^3.1.5", 11 | "express": "^4.17.1", 12 | "express-ejs-layouts": "^2.5.0", 13 | "jsonwebtoken": "^8.5.1", 14 | "mongoose": "^5.10.6", 15 | "morgan": "^1.10.0", 16 | "nodemailer": "^6.4.13", 17 | "nodeman": "^1.1.2", 18 | "validator": "^13.1.17" 19 | }, 20 | "devDependencies": { 21 | "nodemon": "^2.0.4" 22 | }, 23 | "scripts": { 24 | "start": "node app.js", 25 | "dev": "nodemon app.js" 26 | }, 27 | "keywords": [], 28 | "author": "", 29 | "license": "ISC" 30 | } 31 | -------------------------------------------------------------------------------- /public/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifElseCode/projectBlog/05e5e946281826d8d9c359ba485a88081c443bb3/public/.DS_Store -------------------------------------------------------------------------------- /public/css/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifElseCode/projectBlog/05e5e946281826d8d9c359ba485a88081c443bb3/public/css/.gitkeep -------------------------------------------------------------------------------- /public/css/404/404.css: -------------------------------------------------------------------------------- 1 | .pnf { 2 | height: 550px; 3 | display: flex; 4 | flex-direction: column; 5 | justify-content: center; 6 | align-items: center; 7 | } 8 | 9 | .pnf--title { 10 | font-size: 7.5rem; 11 | font-weight: 600; 12 | } 13 | 14 | .pnf--sub-title { 15 | font-size: 2.5rem; 16 | } 17 | 18 | .pnf--description { 19 | font-size: 1.5rem; 20 | width: 55%; 21 | text-align: center; 22 | } 23 | 24 | .pnf--button { 25 | font-size: 1.5rem; 26 | border-radius: 3rem; 27 | padding: 1rem 3rem; 28 | background-color: var(--brand-color); 29 | color: white; 30 | margin-top: 2rem; 31 | } 32 | 33 | .pnf--image { 34 | position: absolute; 35 | bottom: -5%; 36 | right: -2.5%; 37 | } 38 | -------------------------------------------------------------------------------- /public/css/about/about.css: -------------------------------------------------------------------------------- 1 | about-body{ 2 | padding: 1.5em; 3 | } 4 | 5 | .about-heading { 6 | font-size: 3em; 7 | text-align: center; 8 | color: black; 9 | text-transform: uppercase; 10 | text-decoration: underline; 11 | font-weight: 650; 12 | padding-top: 1em; 13 | font-family: 'Alegreya', serif; 14 | } 15 | 16 | .about-intro{ 17 | font-size: 1.75em; 18 | font-style: italic; 19 | text-align: center; 20 | color: #707070; 21 | padding: 1.5em; 22 | } 23 | 24 | .about-description{ 25 | font-size: 1.75em; 26 | text-align: center; 27 | padding: 2em; 28 | } 29 | 30 | .about-name { 31 | font-size: 3em; 32 | text-align: center; 33 | color: var(--brand-color); 34 | text-decoration: underline; 35 | font-weight: 600; 36 | } 37 | 38 | .about-role { 39 | font-size: 2em; 40 | text-align: center; 41 | color: salmon; 42 | text-decoration: underline; 43 | font-weight: 300; 44 | } 45 | 46 | .person-container { 47 | padding: 0.75rem; 48 | } 49 | 50 | .about-picture { 51 | display: block; 52 | margin-left: auto; 53 | margin-right: auto; 54 | width: 250px; 55 | height: 250px; 56 | border-radius: 50%; 57 | border: 2px solid var(--brand-color); 58 | } 59 | 60 | .about-person-info { 61 | font-size: 1.5em; 62 | font-style: italic; 63 | text-align: center; 64 | color: seagreen; 65 | padding: 1.5em; 66 | } 67 | 68 | .about-hyperlink { 69 | color: maroon; 70 | font-weight: 600; 71 | } 72 | 73 | .about-special-thanks { 74 | font-size: 2.25em; 75 | text-align: center; 76 | color: green; 77 | text-transform: uppercase; 78 | text-decoration: underline; 79 | font-weight: 350; 80 | padding-top: 1em; 81 | } 82 | 83 | .head { 84 | font-family: "Lato", sans-serif; 85 | display: flex; 86 | justify-content: space-between; 87 | align-content: center; 88 | } 89 | 90 | .head-contact { 91 | margin: 4rem; 92 | font-size: 2rem; 93 | font-weight: bold; 94 | } 95 | 96 | .head-home { 97 | margin: 4rem 98 | } 99 | 100 | .fa-home { 101 | color: var(--brand-color); 102 | } -------------------------------------------------------------------------------- /public/css/auth/login.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | margin: 0; 4 | padding: 0; 5 | } 6 | 7 | .box { 8 | height: 100%; 9 | display: flex; 10 | flex-direction: column; 11 | justify-content: center; 12 | align-items: center; 13 | } 14 | 15 | .box h1 { 16 | font-size: 40px; 17 | color: var(--brand-color); 18 | text-transform: uppercase; 19 | font-weight: 700; 20 | } 21 | 22 | .box input[type="text"], 23 | .box input[type="password"] { 24 | border: 0; 25 | background: none; 26 | display: block; 27 | text-align: center; 28 | border: 2px solid var(--brand-color); 29 | padding: 14px 10px; 30 | width: 350px; 31 | outline: none; 32 | color: black; 33 | border-radius: 10px; 34 | transition: 0.25s; 35 | } 36 | 37 | .box input[type="password"] { 38 | margin-top: 0; 39 | } 40 | 41 | .emailContainer, 42 | .passwordContainer { 43 | position: relative; 44 | display: flex; 45 | margin: 20px auto; 46 | } 47 | 48 | .passwordContainer { 49 | margin-top: 0; 50 | } 51 | 52 | .box input[type="text"]:focus, 53 | .box input[type="password"]:focus { 54 | width: 375px; 55 | border-color: rgb(86, 199, 252); 56 | } 57 | 58 | .box input[type="submit"], 59 | .box input[type="button"] { 60 | border: 0; 61 | background: none; 62 | display: inline-block; 63 | margin: auto; 64 | width: 175px; 65 | text-align: center; 66 | border: 2px solid var(--brand-color); 67 | padding: 14px 40px; 68 | outline: none; 69 | color: black; 70 | border-radius: 10px; 71 | transition: 0.25s; 72 | cursor: pointer; 73 | } 74 | 75 | .box input[type="submit"]:hover, 76 | .box input[type="button"]:hover { 77 | background: var(--brand-color); 78 | border: 2px dashed var(--brand-color); 79 | color: white; 80 | } 81 | 82 | .error-message { 83 | visibility: hidden; 84 | text-align: center; 85 | margin-top: 40px; 86 | margin-left: 150px; 87 | margin-right: 150px; 88 | border: 2px solid red; 89 | } 90 | 91 | .fa-user { 92 | position: absolute; 93 | top: 50%; 94 | transform: translateY(-50%); 95 | left: 20px; 96 | } 97 | 98 | .fa-unlock-alt { 99 | position: absolute; 100 | top: 50%; 101 | transform: translateY(-50%); 102 | left: 20px; 103 | } 104 | -------------------------------------------------------------------------------- /public/css/auth/signup.css: -------------------------------------------------------------------------------- 1 | .box { 2 | height: 100%; 3 | display: flex; 4 | flex-direction: column; 5 | justify-content: center; 6 | align-items: center; 7 | } 8 | 9 | .box h1 { 10 | font-size: 40px; 11 | color: var(--brand-color); 12 | text-transform: uppercase; 13 | font-weight: 700; 14 | } 15 | 16 | .box input[type="text"], 17 | .box input[type="email"], 18 | .box input[type="password"] { 19 | border: 0; 20 | background: none; 21 | display: block; 22 | text-align: center; 23 | border: 2px solid var(--brand-color); 24 | padding: 14px 10px; 25 | width: 350px; 26 | outline: none; 27 | color: black; 28 | border-radius: 10px; 29 | transition: 0.25s; 30 | } 31 | 32 | .box input[type="password"] { 33 | margin-top: 0; 34 | } 35 | 36 | .usernameContainer, 37 | .emailContainer, 38 | .passwordContainer { 39 | position: relative; 40 | display: flex; 41 | margin: 20px auto; 42 | } 43 | 44 | .emailContainer { 45 | margin-top: 0; 46 | } 47 | 48 | .passwordContainer { 49 | margin-top: 0; 50 | } 51 | 52 | .box input[type="text"]:focus, 53 | .box input[type="email"]:focus, 54 | .box input[type="password"]:focus { 55 | width: 375px; 56 | border-color: rgb(86, 199, 252); 57 | } 58 | 59 | .box input[type="submit"], 60 | .box input[type="button"] { 61 | border: 0; 62 | background: none; 63 | display: inline-block; 64 | margin: auto; 65 | width: 175px; 66 | text-align: center; 67 | border: 2px solid var(--brand-color); 68 | padding: 14px 40px; 69 | outline: none; 70 | color: black; 71 | border-radius: 10px; 72 | transition: 0.25s; 73 | cursor: pointer; 74 | } 75 | 76 | .box input[type="submit"]:hover, 77 | .box input[type="button"]:hover { 78 | background: var(--brand-color); 79 | border: 2px dashed var(--brand-color); 80 | color: white; 81 | } 82 | 83 | .fa-user { 84 | position: absolute; 85 | top: 50%; 86 | transform: translateY(-50%); 87 | left: 20px; 88 | } 89 | 90 | .fa-unlock-alt { 91 | position: absolute; 92 | top: 50%; 93 | transform: translateY(-50%); 94 | left: 20px; 95 | } 96 | 97 | .fa-at { 98 | position: absolute; 99 | top: 50%; 100 | transform: translateY(-50%); 101 | left: 20px; 102 | } -------------------------------------------------------------------------------- /public/css/base/global.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap'); 2 | 3 | html { 4 | /* This defines what 1rem is, try to keep everything in rem */ 5 | font-size: 62.5%; 6 | } 7 | 8 | body { 9 | font-family: 'Poppins', sans-serif; 10 | line-height: 1.5; 11 | color: #333; 12 | } 13 | 14 | * { 15 | box-sizing: border-box; 16 | padding: 0; 17 | margin: 0; 18 | } 19 | 20 | ul { 21 | list-style-type: none; 22 | } 23 | 24 | a { 25 | text-decoration: none; 26 | color: #333; 27 | } 28 | 29 | img { 30 | width: 100%; 31 | } 32 | 33 | code, pre { 34 | background-color: #333; 35 | color: #fff; 36 | padding: 10px; 37 | } -------------------------------------------------------------------------------- /public/css/blogs/blogs.css: -------------------------------------------------------------------------------- 1 | /* 2 | .blogs--header { 3 | height: 9rem; 4 | border-bottom: 1px dashed var(--grey); 5 | display: flex; 6 | justify-content: space-between; 7 | align-items: center; 8 | padding: 0 2.5rem; 9 | } 10 | 11 | .blogs--header > h1 { 12 | font-size: 2.5rem; 13 | font-weight: 400; 14 | } 15 | 16 | .blogs--header > i { 17 | font-size: 3rem; 18 | color: var(--brand-color); 19 | } 20 | 21 | .blogs--container { 22 | padding: 0 2.5rem; 23 | } 24 | 25 | .blogs--blog { 26 | padding: 4rem 0; 27 | } 28 | 29 | .blogs--blog:first-of-type { 30 | padding-top: 5rem; 31 | } 32 | .blogs--blog:last-of-type { 33 | padding-bottom: 5rem; 34 | } 35 | 36 | .blogs--image { 37 | max-height: 628px; 38 | width: 100%; 39 | object-fit: contain; 40 | } 41 | 42 | .blogs--title { 43 | font-size: 3.25rem; 44 | font-weight: 600; 45 | line-height: 4.5rem; 46 | margin: 1.5rem 0; 47 | } 48 | 49 | .blogs--info { 50 | font-size: 1.3rem; 51 | color: var(--grey); 52 | font-weight: 500; 53 | margin-bottom: 1.5rem; 54 | } 55 | 56 | .blogs--info > span { 57 | color: var(--brand-color); 58 | } 59 | 60 | .blogs--content { 61 | margin-bottom: 1.5rem; 62 | } 63 | 64 | .blogs--link { 65 | font-size: 1.5em; 66 | color: var(--brand-color); 67 | } 68 | 69 | .blogs--content > p { 70 | color: var(--grey); 71 | font-size: 1.6rem; 72 | margin: 2.5rem 0; 73 | } 74 | 75 | .blogs--content > p:first-child { 76 | margin-top: 0; 77 | } 78 | .blogs--content > p:last-child { 79 | margin-bottom: 0; 80 | } */ 81 | 82 | 83 | /* Extra small devices (portrait phones, less than 576px) */ 84 | /* No media query for `xs` since this is the default in Bootstrap */ 85 | .blogs--header { 86 | height: 8rem; 87 | display: flex; 88 | justify-content: space-between; 89 | align-items: center; 90 | padding: 0 2.5rem; 91 | border-bottom: 1px dashed var(--grey); 92 | } 93 | 94 | .blogs--header > h1 { 95 | font-size: 2.1rem; 96 | line-height: 1.524; 97 | font-weight: 500; 98 | } 99 | 100 | .blogs--header > i { 101 | font-size: 2.6rem; 102 | color: var(--brand-color); 103 | } 104 | 105 | .blogs--container { 106 | padding: 0 2.5rem; 107 | } 108 | 109 | .blogs--blog { 110 | padding: 4rem 0; 111 | } 112 | 113 | .blogs--image { 114 | max-height: 628px; 115 | width: 100%; 116 | } 117 | 118 | .blogs--title { 119 | font-size: 2.1rem; 120 | font-weight: 600; 121 | line-height: 1.524; 122 | margin: 1.5rem 0; 123 | } 124 | 125 | .blogs--info { 126 | font-size: 1.3rem; 127 | line-height: 1.615; 128 | color: var(--grey); 129 | } 130 | 131 | .blogs--info > span { 132 | color: var(--brand-color); 133 | } 134 | 135 | .blogs--content > p { 136 | color: var(--grey); 137 | font-size: 1.3rem; 138 | line-height: 1.615; 139 | margin: 1.5rem 0; 140 | } 141 | 142 | .blogs--link { 143 | font-size: 1.3rem; 144 | line-height: 1.615; 145 | color: var(--brand-color); 146 | } 147 | 148 | /* Small devices (landscape phones, 576px and up) */ 149 | @media (min-width: 576px) { 150 | .blogs--header > h1 { 151 | font-size: 2.6rem; 152 | line-height: 1.577; 153 | } 154 | 155 | .blogs--header > i { 156 | font-size: 3.1rem; 157 | } 158 | 159 | .blogs--title { 160 | font-size: 2.6rem; 161 | line-height: 1.577; 162 | } 163 | 164 | .blogs--info { 165 | font-size: 1.6rem; 166 | line-height: 1.625; 167 | } 168 | 169 | .blogs--content > p { 170 | font-size: 1.6rem; 171 | line-height: 1.625; 172 | } 173 | 174 | .blogs--link { 175 | font-size: 1.6rem; 176 | line-height: 1.625; 177 | } 178 | } 179 | 180 | /* Medium devices (tablets, 768px and up) */ 181 | @media (min-width: 768px) { 182 | .blogs--header > h1 { 183 | font-size: 3rem; 184 | line-height: 1.977; 185 | } 186 | 187 | .blogs--header > i { 188 | font-size: 3.5rem; 189 | } 190 | 191 | .blogs--title { 192 | font-size: 3rem; 193 | line-height: 1.977; 194 | } 195 | 196 | .blogs--info { 197 | font-size: 2.1rem; 198 | line-height: 1.714; 199 | } 200 | 201 | .blogs--content > p { 202 | font-size: 2.1rem; 203 | line-height: 1.714; 204 | } 205 | 206 | .blogs--link { 207 | font-size: 2.1rem; 208 | line-height: 1.714; 209 | } 210 | } 211 | 212 | /* Large devices (desktops, 992px and up) */ 213 | @media (min-width: 992px) { 214 | 215 | } 216 | 217 | /* Extra large devices (large desktops, 1200px and up) */ 218 | @media (min-width: 1200px) { 219 | 220 | } -------------------------------------------------------------------------------- /public/css/contact/contact.css: -------------------------------------------------------------------------------- 1 | /* 460 to 650*/ 2 | @media only screen and (max-width: 600px){ 3 | .contact-container { 4 | display: flex; 5 | align-content: center; 6 | justify-content: space-evenly; 7 | flex-direction: column; 8 | flex-wrap: wrap; 9 | } 10 | 11 | .contact-header { 12 | display: flex; 13 | justify-content: space-around; 14 | margin-top: 1rem; 15 | } 16 | .contact-h1 { 17 | margin: 1rem; 18 | font-size: 1.7rem; 19 | font-weight: 700; 20 | } 21 | .contact-home-icon { 22 | color: var(--brand-color); 23 | margin-right: 1.5rem; 24 | margin-top: 0.8rem; 25 | } 26 | .h-break { 27 | border: 0; 28 | height: 1px; 29 | background-image: -webkit-linear-gradient(left, #f0f0f0, #8c8b8b, #f0f0f0); 30 | background-image: -moz-linear-gradient(left, #f0f0f0, #8c8b8b, #f0f0f0); 31 | background-image: -ms-linear-gradient(left, #f0f0f0, #8c8b8b, #f0f0f0); 32 | background-image: -o-linear-gradient(left, #f0f0f0, #8c8b8b, #f0f0f0); 33 | } 34 | 35 | .contact-head2 { 36 | font-size: 2rem; 37 | font-weight: 900; 38 | margin: 3rem; 39 | } 40 | 41 | .line { 42 | color: var(--brand-color); 43 | } 44 | 45 | /*contact info*/ 46 | .contact-info { 47 | display: flex; 48 | justify-content: space-evenly; 49 | align-content: flex-start; 50 | align-items: center; 51 | flex-direction: column; 52 | } 53 | 54 | .contact-mobile, 55 | .contact-envelope, 56 | .contact-clock .contact-para { 57 | letter-spacing: 0.2rem; 58 | margin-bottom: 0.4rem; 59 | } 60 | 61 | .contact-mobile { 62 | color: lightseagreen; 63 | } 64 | 65 | .contact-envelope { 66 | color: rgb(231, 87, 61); 67 | } 68 | 69 | .contact-clock { 70 | color: turquoise; 71 | } 72 | 73 | /*form styles*/ 74 | .contact-form { 75 | display: flex; 76 | justify-content: center; 77 | align-content: center; 78 | flex-direction: column; 79 | align-items: center; 80 | } 81 | 82 | ::-webkit-input-placeholder { 83 | font-family: "Lato", sans-serif; 84 | font-size: 0.9rem; 85 | color: #959596; 86 | text-align: left; 87 | font-weight: bold; 88 | } 89 | 90 | .form-name { 91 | border-radius: 1rem; 92 | box-shadow: 0 0 2px hsl(0, 0%, 80%), 0 0 2px hsl(0, 0%, 90%); 93 | padding: 1rem 0 1rem 1rem; 94 | width: 70%; 95 | margin-bottom: .5rem; 96 | } 97 | 98 | .form-email { 99 | border-radius: 1rem; 100 | box-shadow: 0 0 2px hsl(0, 0%, 80%), 0 0 2px hsl(0, 0%, 90%); 101 | padding: 1rem 0 1rem 1rem; 102 | width: 70%; 103 | } 104 | 105 | .form-message { 106 | border-radius: 1rem; 107 | margin-top: 1rem; 108 | overflow: hidden; 109 | width: 75%; 110 | align-self: center; 111 | box-shadow: 0 0 2px hsl(0, 0%, 80%), 0 0 2px hsl(0, 0%, 90%); 112 | } 113 | 114 | .form-send { 115 | margin-top: 2rem; 116 | align-self: center; 117 | text-align: center; 118 | border-radius: 50%; 119 | border: var(--brand-color); 120 | border-style: dashed; 121 | height: 7rem; 122 | width: 7rem; 123 | margin-bottom: 4rem; 124 | font-family: "Lato", sans-serif; 125 | } 126 | } 127 | 128 | 129 | /*650 to 820*/ 130 | @media only screen and (min-width: 600px) and (max-width: 900px) { 131 | /* Header settings */ 132 | .contact-header { 133 | display: flex; 134 | justify-content: space-between; 135 | align-content: center; 136 | } 137 | 138 | .contact-h1 { 139 | font-size: 2rem; 140 | font-weight: 900; 141 | margin-left: 1.2rem; 142 | } 143 | 144 | .contact-home-icon { 145 | color: var(--brand-color); 146 | margin-right: 1.5rem; 147 | margin-top: 0.8rem; 148 | } 149 | 150 | .h-break { 151 | border: 0; 152 | height: 1px; 153 | background-image: -webkit-linear-gradient(left, #f0f0f0, #8c8b8b, #f0f0f0); 154 | background-image: -moz-linear-gradient(left, #f0f0f0, #8c8b8b, #f0f0f0); 155 | background-image: -ms-linear-gradient(left, #f0f0f0, #8c8b8b, #f0f0f0); 156 | background-image: -o-linear-gradient(left, #f0f0f0, #8c8b8b, #f0f0f0); 157 | } 158 | 159 | .contact-head2 { 160 | font-size: 2.3rem; 161 | font-weight: 900; 162 | margin-top: 2rem; 163 | margin-left: 0.8rem; 164 | } 165 | 166 | /* contact info style */ 167 | .contact-info { 168 | display: flex; 169 | justify-content: space-between; 170 | padding: 1.5rem; 171 | } 172 | 173 | .contact-mobile, 174 | .contact-envelope, 175 | .contact-clock, 176 | .contact-para { 177 | align-self: center; 178 | } 179 | 180 | .contact-mobile { 181 | color: lightseagreen; 182 | } 183 | 184 | .contact-envelope { 185 | color: rgb(231, 87, 61); 186 | } 187 | 188 | .contact-clock { 189 | color: turquoise; 190 | } 191 | /*Form styles*/ 192 | ::-webkit-input-placeholder { 193 | font-family: "Lato", sans-serif; 194 | font-size: 14px; 195 | color: #959596; 196 | text-align: left; 197 | font-weight: bold; 198 | } 199 | 200 | .form-contact { 201 | margin-top: 2rem; 202 | display: flex; 203 | justify-content: space-around; 204 | flex-flow: row wrap; 205 | } 206 | 207 | .contact-form { 208 | margin-top: 1rem; 209 | display: flex; 210 | width: 80% 211 | justify-content: center; 212 | flex-flow: column wrap; 213 | align-content: center; 214 | } 215 | 216 | /* form styles */ 217 | .form-name, 218 | .form-email, 219 | .form-message { 220 | padding: 0.8rem; 221 | align-self: center; 222 | margin: 0.7rem; 223 | } 224 | 225 | .form-name { 226 | border-radius: 1rem; 227 | box-shadow: 0 0 2px hsl(0, 0%, 80%), 0 0 2px hsl(0, 0%, 90%); 228 | padding: 1rem 0 1rem 1rem; 229 | width: 85%; 230 | } 231 | 232 | .form-email { 233 | border-radius: 1rem; 234 | box-shadow: 0 0 2px hsl(0, 0%, 80%), 0 0 2px hsl(0, 0%, 90%); 235 | padding: 1rem 0 1rem 1rem; 236 | width: 85%; 237 | } 238 | 239 | .form-message { 240 | border-radius: 1rem; 241 | margin-top: 1rem; 242 | overflow: hidden; 243 | width: 90%; 244 | align-self: center; 245 | box-shadow: 0 0 2px hsl(0, 0%, 80%), 0 0 2px hsl(0, 0%, 90%); 246 | } 247 | 248 | .form-send { 249 | text-align: center; 250 | align-self: center; 251 | border-radius: 50%; 252 | border: var(--brand-color); 253 | border-style: dashed; 254 | height: 7rem; 255 | width: 7rem; 256 | margin-top: 2.5rem; 257 | margin-bottom: 3rem; 258 | font-family: "Lato", sans-serif; 259 | } 260 | } 261 | 262 | 263 | @media only screen and (min-width: 900px) { 264 | 265 | /*header*/ 266 | .h-break { 267 | border: 0; 268 | height: 1px; 269 | background-image: -webkit-linear-gradient(left, #f0f0f0, #8c8b8b, #f0f0f0); 270 | background-image: -moz-linear-gradient(left, #f0f0f0, #8c8b8b, #f0f0f0); 271 | background-image: -ms-linear-gradient(left, #f0f0f0, #8c8b8b, #f0f0f0); 272 | background-image: -o-linear-gradient(left, #f0f0f0, #8c8b8b, #f0f0f0); 273 | } 274 | 275 | .contact-header { 276 | font-family: "Lato", sans-serif; 277 | display: flex; 278 | justify-content: space-between; 279 | align-content: center; 280 | } 281 | 282 | .contact-h1 { 283 | margin: 3rem; 284 | font-size: 3rem; 285 | font-weight: 700; 286 | align-self: center; 287 | } 288 | 289 | .contact-home-icon { 290 | margin-top: 2rem; 291 | margin-right: 4rem ; 292 | color: var(--brand-color); 293 | } 294 | 295 | /*Second Heading style*/ 296 | .contact-head2 { 297 | font-size: 4rem; 298 | font-weight: 900; 299 | margin-top: 3.8rem; 300 | margin-left: 1.1rem; 301 | } 302 | 303 | .contact-line { 304 | color: var(--brand-color); 305 | } 306 | 307 | /*contact styling*/ 308 | .contact-info { 309 | display: flex; 310 | justify-content: space-between; 311 | margin: 2rem; 312 | padding: 2rem; 313 | } 314 | 315 | .contact-mobile, .contact-envelope, .contact-clock, .contact-para { 316 | letter-spacing: .11rem; 317 | font-size: 1.5rem; 318 | align-self: flex-start; 319 | } 320 | .contact-mobile{ 321 | color: lightseagreen; 322 | font-size: 3.3rem; 323 | } 324 | 325 | .contact-envelope { 326 | color: rgb(231, 87, 61); 327 | font-size: 3.3rem; 328 | } 329 | 330 | .contact-clock{ 331 | color: turquoise; 332 | font-size: 3.3rem; 333 | } 334 | 335 | /*Form styles*/ 336 | 337 | .contact-form { 338 | margin-top: 1rem; 339 | display: flex; 340 | justify-content: space-around; 341 | /* align-items: flex-start; */ 342 | flex-flow: column wrap; 343 | align-content: center; 344 | } 345 | 346 | .form-name, .form-email { 347 | box-shadow: 0 0 2px hsl(0, 0%, 80%), 0 0 2px hsl(0, 0%, 90%); 348 | padding: 1rem 0 1rem 1rem; 349 | border-radius: .8rem; 350 | } 351 | 352 | .form-name { 353 | margin-bottom: .3rem; 354 | } 355 | ::-webkit-input-placeholder { 356 | font-family: "Lato", sans-serif; 357 | font-size: 14px; 358 | color: #959596; 359 | text-align: left; 360 | font-weight: bold; 361 | } 362 | 363 | .form-message { 364 | margin-top: 1rem; 365 | border-radius: .8rem; 366 | overflow: hidden; 367 | width: 80%; 368 | align-self: center; 369 | box-shadow: 0 0 2px hsl(0, 0%, 80%), 0 0 2px hsl(0, 0%, 90%); 370 | } 371 | .form-send { 372 | margin-top: 2.5rem; 373 | margin-bottom: 3rem; 374 | align-self: center; 375 | text-align: center; 376 | border-radius: 50%; 377 | border: var(--brand-color); 378 | border-style: dashed; 379 | height: 7rem; 380 | width: 7rem; 381 | font-family: "Lato", sans-serif; 382 | } 383 | } -------------------------------------------------------------------------------- /public/css/dashboard/blog-posts.css: -------------------------------------------------------------------------------- 1 | /* Styles for Blog post Listings */ 2 | .dashboard-blog-posts--header { 3 | display: flex; 4 | justify-content: space-between; 5 | align-items: center; 6 | margin-bottom: 1.5rem; 7 | } 8 | 9 | .dashboard-blog-posts--title { 10 | font-size: 2.5rem; 11 | letter-spacing: 0.1rem; 12 | } 13 | 14 | .dashboard-blog-posts--title > span { 15 | font-size: 1.5rem; 16 | opacity: 0.8; 17 | } 18 | 19 | .dashboard-blog-posts--header > a { 20 | padding: 1rem 1.5rem; 21 | background-color: #2f477b; 22 | color: #fff; 23 | font-size: 1.5rem; 24 | border-radius: 0.5rem; 25 | cursor: pointer; 26 | } 27 | 28 | .dashboard-blog-posts--table { 29 | width: 100%; 30 | } 31 | 32 | .dashboard-blog-posts--table th { 33 | font-weight: bold; 34 | letter-spacing: 0.1rem; 35 | } 36 | 37 | .dashboard-blog-posts--table th, 38 | .dashboard-blog-posts--table td { 39 | font-size: 1.25rem; 40 | padding: 1rem; 41 | width: 25%; 42 | } 43 | 44 | .dashboard-blog-posts--table tr:nth-child(even) { 45 | background-color: #f2f2f2; 46 | } 47 | 48 | .dashboard-blog-posts--table th { 49 | padding-top: 12px; 50 | padding-bottom: 12px; 51 | text-align: left; 52 | background-color: #2f477b; 53 | color: white; 54 | } 55 | 56 | .dashboard-blog-posts--actions { 57 | display: flex; 58 | } 59 | 60 | .dashboard-blog-posts--button { 61 | padding: 0.5rem 2rem; 62 | border-radius: 0.5rem; 63 | cursor: pointer; 64 | } 65 | 66 | .dashboard-blog-posts--button:nth-of-type(1) { 67 | margin-right: 1rem; 68 | } 69 | 70 | .dashboard-blog-posts--button__green { 71 | background-color: #81c784; 72 | } 73 | 74 | .dashboard-blog-posts--button__red { 75 | background-color: #e57373; 76 | } 77 | 78 | .dashboard-blog-posts--button__black { 79 | background-color: var(--black); 80 | } 81 | 82 | /* Styles for create blog post Form */ 83 | .dashboard-blog-post--form > input, 84 | .dashboard-blog-post--form > textarea { 85 | width: 100%; 86 | padding: 1rem; 87 | font-size: 1.5rem; 88 | margin-bottom: 2.5rem; 89 | border: 1px solid var(--black); 90 | } 91 | 92 | .dashboard-blog-post--form > button { 93 | padding: 1rem 1.5rem; 94 | background-color: #2f477b; 95 | color: #fff; 96 | font-size: 1.5rem; 97 | border-radius: 0.5rem; 98 | cursor: pointer; 99 | } 100 | -------------------------------------------------------------------------------- /public/css/dashboard/table.css: -------------------------------------------------------------------------------- 1 | /* Styles for Blog post Listings */ 2 | .dashboard-header { 3 | display: flex; 4 | justify-content: space-between; 5 | align-items: center; 6 | margin-bottom: 1.5rem; 7 | } 8 | 9 | .dashboard-header__title { 10 | font-size: 2.5rem; 11 | letter-spacing: 0.1rem; 12 | } 13 | 14 | .dashboard-header__title > span { 15 | font-size: 1.5rem; 16 | opacity: 0.8; 17 | } 18 | 19 | 20 | .table { 21 | width: 100%; 22 | } 23 | 24 | .table__head { 25 | font-weight: bold; 26 | letter-spacing: 0.1rem; 27 | } 28 | 29 | .table__head, .table__data{ 30 | font-size: 1.25rem; 31 | padding: 1rem; 32 | width: 25%; 33 | } 34 | 35 | .table__row:nth-child(even) { 36 | background-color: #f2f2f2; 37 | } 38 | 39 | .table__header { 40 | padding-top: 12px; 41 | padding-bottom: 12px; 42 | text-align: left; 43 | background-color: #2f477b; 44 | color: white; 45 | } 46 | 47 | .btn { 48 | font-weight: 400; 49 | font-size: 1rem; 50 | color: #212529; 51 | text-align: center; 52 | /* padding: .5rem .75rem; */ 53 | padding: 1rem 1.5rem; 54 | line-height: 1.5; 55 | border-radius: .25rem; 56 | cursor: pointer; 57 | } 58 | 59 | .btn--dark { 60 | color: #fff; 61 | background-color: #343a40; 62 | } 63 | 64 | .btn--red { 65 | color: #fff; 66 | background-color: #dc3545; 67 | } 68 | 69 | -------------------------------------------------------------------------------- /public/css/helpers/variables.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --bg-color: #fff; 3 | --brand-color: #1abc9c; 4 | --black: #171717; 5 | --grey: #A9A9A9; 6 | } 7 | -------------------------------------------------------------------------------- /public/css/layouts/dashboard-layout.css: -------------------------------------------------------------------------------- 1 | /* .dashboard-sidebar { 2 | width: 250px; 3 | height: 100%; 4 | position: fixed; 5 | border-right: 1px solid lightgrey; 6 | } 7 | 8 | .dashboard-sidebar__user { 9 | padding: 1.5rem; 10 | display: flex; 11 | align-items: center; 12 | background-color: rgba(26, 188, 156, .25); 13 | } 14 | 15 | .dashboard-sidebar__initials { 16 | background-color: var(--brand-color); 17 | width: 50px; 18 | height: 50px; 19 | border-radius: 50%; 20 | display: flex; 21 | justify-content: center; 22 | align-items: center; 23 | font-size: 1.75rem; 24 | letter-spacing: 0.1rem; 25 | margin-right: 1.25rem; 26 | } 27 | 28 | .dashboard-sidebar__info { 29 | letter-spacing: 0.1rem; 30 | } 31 | 32 | .dashboard-sidebar__info > div:nth-child(1) { 33 | font-size: 1.5rem; 34 | } 35 | 36 | .dashboard-sidebar__info > div:nth-child(2) { 37 | color: var(--brand-color); 38 | font-weight: lighter; 39 | font-size: 1.25rem; 40 | } 41 | 42 | .dashboard-sidebar__nav { 43 | font-size: 1.5rem; 44 | letter-spacing: 0.1rem; 45 | } 46 | 47 | .dashboard-sidebar__nav > a { 48 | display: flex; 49 | align-items: center; 50 | padding: 1.5rem; 51 | transition: all 0.4s ease-in-out; 52 | } 53 | 54 | .dashboard-sidebar__nav > a:hover { 55 | background-color: var(--brand-color); 56 | color: #fff; 57 | } 58 | 59 | .dashboard-sidebar__nav > a > i { 60 | width: 30px; 61 | margin-right: 1rem; 62 | } 63 | 64 | .dashboard-content { 65 | margin-left: 250px; 66 | padding: 1.5rem; 67 | position: relative; 68 | } */ 69 | 70 | 71 | /* Extra small devices (portrait phones, less than 576px */ 72 | /* No media query for `xs` since this is the default in Bootstrap */ 73 | body { 74 | padding: 3rem 1rem; 75 | } 76 | 77 | .dashboard-sidebar__user { 78 | padding: 1.5rem; 79 | display: flex; 80 | align-items: center; 81 | background-color: rgba(26, 188, 156, .25); 82 | } 83 | 84 | .dashboard-sidebar__initials { 85 | background-color: var(--brand-color); 86 | width: 50px; 87 | height: 50px; 88 | border-radius: 50%; 89 | display: flex; 90 | justify-content: center; 91 | align-items: center; 92 | font-size: 1.75rem; 93 | letter-spacing: 0.1rem; 94 | margin-right: 1.25rem; 95 | } 96 | 97 | .dashboard-sidebar__info { 98 | letter-spacing: 0.1rem; 99 | } 100 | 101 | .dashboard-sidebar__info > div:nth-child(1) { 102 | font-size: 1.5rem; 103 | } 104 | 105 | .dashboard-sidebar__info > div:nth-child(2) { 106 | color: var(--brand-color); 107 | font-weight: lighter; 108 | font-size: 1.25rem; 109 | } 110 | 111 | .hamburger { 112 | height: 50px; 113 | width: 50px; 114 | margin-left: auto; 115 | display: flex; 116 | flex-direction: column; 117 | justify-content: center; 118 | align-items: center; 119 | cursor: pointer; 120 | } 121 | 122 | .hamburger > div { 123 | background-color: var(--black); 124 | height: 2px; 125 | width: 22.5px; 126 | margin: 3px; 127 | } 128 | 129 | .dashboard-sidebar__nav { 130 | display: flex; 131 | font-size: 1.5rem; 132 | letter-spacing: 0.1rem; 133 | overflow-x: scroll; 134 | } 135 | 136 | .dashboard-sidebar__nav::-webkit-scrollbar { 137 | display: none; 138 | } 139 | 140 | .dashboard-sidebar__nav > a { 141 | display: flex; 142 | align-items: center; 143 | padding: 1.5rem; 144 | transition: all 0.4s ease-in-out; 145 | } 146 | 147 | .dashboard-sidebar__nav > a:hover { 148 | background-color: var(--brand-color); 149 | color: #fff; 150 | } 151 | 152 | .dashboard-sidebar__nav > a > i { 153 | width: 30px; 154 | margin-right: 1rem; 155 | } 156 | 157 | .dashboard-content { 158 | position: relative; 159 | overflow-x: hidden; 160 | } 161 | 162 | /* Small devices (landscape phones, 576px and up) */ 163 | @media (min-width: 576px) { 164 | 165 | 166 | } 167 | 168 | /* Medium devices (tablets, 768px and up) */ 169 | @media (min-width: 768px) { 170 | body { 171 | padding: 0; 172 | } 173 | 174 | .dashboard-sidebar { 175 | width: 250px; 176 | height: 100%; 177 | position: fixed; 178 | border-right: 1px solid lightgrey; 179 | } 180 | 181 | .hamburger { 182 | display: none; 183 | } 184 | 185 | .dashboard-sidebar__nav { 186 | display: block; 187 | } 188 | 189 | .dashboard-content { 190 | margin-left: 250px; 191 | padding: 1.5rem; 192 | position: relative; 193 | } 194 | } 195 | 196 | /* Large devices (desktops, 992px and up) */ 197 | @media (min-width: 992px) { 198 | 199 | } 200 | 201 | /* Extra large devices (large desktops, 1200px and up) */ 202 | @media (min-width: 1200px) { 203 | 204 | } 205 | -------------------------------------------------------------------------------- /public/css/layouts/main-layout.css: -------------------------------------------------------------------------------- 1 | /* Extra small devices (portrait phones, less than 576px) */ 2 | /* No media query for `xs` since this is the default in Bootstrap */ 3 | body { 4 | background-color: #f2f0f2; 5 | padding: 3rem 1rem; 6 | } 7 | 8 | .main-nav, 9 | .main-body, 10 | .main-footer { 11 | -webkit-box-shadow: 0px 0px 54px 5px rgba(0, 0, 0, 0.19); 12 | -moz-box-shadow: 0px 0px 54px 5px rgba(0, 0, 0, 0.19); 13 | box-shadow: 0px 0px 54px 5px rgba(0, 0, 0, 0.19); 14 | } 15 | 16 | .main-nav { 17 | background-color: white; 18 | overflow: hidden; 19 | position: relative; 20 | margin-bottom: 2.5rem; 21 | } 22 | 23 | .main-nav--top { 24 | width: 100%; 25 | height: 350px; 26 | object-fit: cover; 27 | } 28 | 29 | .main-nav--links { 30 | background: #f2f0f2; 31 | display: none; 32 | justify-content: space-evenly; 33 | align-items: center; 34 | height: 60px; 35 | } 36 | 37 | .main-nav--links.active { 38 | display: flex; 39 | } 40 | 41 | .main-nav--links > li > a { 42 | font-size: 1.2rem; 43 | } 44 | 45 | .main-nav--middle { 46 | display: flex; 47 | flex-direction: column; 48 | justify-content: center; 49 | align-items: center; 50 | height: 150px; 51 | border-bottom: 1px dotted var(--grey); 52 | } 53 | 54 | .main-nav--middle > h1 { 55 | font-size: 2.5rem; 56 | font-weight: 600; 57 | } 58 | 59 | .main-nav--middle > h2 { 60 | color: var(--grey); 61 | font-size: 2rem; 62 | } 63 | 64 | .main-nav--bottom { 65 | display: flex; 66 | justify-content: space-evenly; 67 | align-items: center; 68 | height: 100px; 69 | color: #fff; 70 | font-size: 1.5rem; 71 | } 72 | 73 | .main-nav--bottom > i { 74 | border-radius: 50%; 75 | width: 4.5rem; 76 | height: 4.5rem; 77 | display: flex; 78 | align-items: center; 79 | justify-content: center; 80 | } 81 | 82 | .main-nav--bottom > i:nth-child(1) { 83 | background-color: #3b5998; 84 | } 85 | 86 | .main-nav--bottom > i:nth-child(2) { 87 | background-color: #1da1f2; 88 | } 89 | 90 | .main-nav--bottom > i:nth-child(3) { 91 | background-color: #2867b2; 92 | } 93 | 94 | .main-nav--bottom > i:nth-child(4) { 95 | background-color: #333333; 96 | } 97 | 98 | .main-nav--burger { 99 | background: var(--brand-color); 100 | height: 50px; 101 | width: 50px; 102 | position: absolute; 103 | top: 0; 104 | right: 0; 105 | display: flex; 106 | flex-direction: column; 107 | justify-content: center; 108 | align-items: center; 109 | cursor: pointer; 110 | } 111 | 112 | .main-nav--burger > div { 113 | background-color: white; 114 | height: 2px; 115 | width: 22.5px; 116 | margin: 3px; 117 | } 118 | 119 | .main-body { 120 | background-color: #fff; 121 | margin-bottom: 5rem; 122 | position: relative; 123 | overflow: hidden; 124 | } 125 | 126 | .main-footer { 127 | height: 150px; 128 | background-image: url('https://cdn.discordapp.com/attachments/752950996707311616/759534699512463400/unknown.png'); 129 | display: flex; 130 | flex-direction: column; 131 | justify-content: center; 132 | align-items: center; 133 | } 134 | 135 | .main-footer > ul { 136 | display: flex; 137 | justify-content: space-between; 138 | margin-bottom: 2rem; 139 | width: 55%; 140 | } 141 | 142 | .main-footer > ul > li, .main-footer > div { 143 | font-size: 1.25rem; 144 | letter-spacing: 0.1rem; 145 | text-align: center; 146 | } 147 | 148 | .main-footer > div { 149 | color: var(--grey); 150 | } 151 | 152 | /* Small devices (landscape phones, 576px and up) */ 153 | @media (min-width: 576px) { 154 | .main-footer > ul { 155 | width: 35%; 156 | } 157 | 158 | .main-footer > ul > li, .main-footer > div { 159 | font-size: 1.50rem; 160 | } 161 | 162 | } 163 | 164 | /* Medium devices (tablets, 768px and up) */ 165 | @media (min-width: 768px) { 166 | .main-nav--top { 167 | height: 450px; 168 | } 169 | 170 | .main-footer > ul { 171 | width: 27.5%; 172 | } 173 | 174 | } 175 | 176 | /* Large devices (desktops, 992px and up) */ 177 | @media (min-width: 992px) { 178 | .main-nav--top { 179 | height: 550px; 180 | } 181 | 182 | .main-footer > ul { 183 | width: 20.5%; 184 | } 185 | } 186 | 187 | /* Extra large devices (large desktops, 1200px and up) */ 188 | @media (min-width: 1200px) { 189 | body { 190 | padding: 6rem 9rem; 191 | } 192 | 193 | .main-nav { 194 | position: fixed; 195 | width: 350px; 196 | } 197 | 198 | .main-nav--top { 199 | height: 275px; 200 | } 201 | 202 | .main-body { 203 | margin-left: 380px; 204 | margin-bottom: 5rem; 205 | } 206 | 207 | .main-footer { 208 | margin-left: 380px; 209 | } 210 | 211 | .main-footer > ul { 212 | width: 30.5%; 213 | } 214 | } -------------------------------------------------------------------------------- /public/css/layouts/user-profile.css: -------------------------------------------------------------------------------- 1 | .user-container { 2 | border: 1px solid black; 3 | border-radius: 2rem; 4 | margin-top: 2rem; 5 | margin-left: auto; 6 | margin-right: auto; 7 | width: 20rem; 8 | text-align: center; 9 | padding: 0 0 2rem 0; 10 | } 11 | 12 | .user-header { 13 | font-weight: 700; 14 | letter-spacing: 0.2rem; 15 | line-height: 1.3; 16 | background-color: var(--brand-color); 17 | border-radius: 2rem 2rem 0 0; 18 | padding: 1rem 0.5rem 1rem 0.5rem; 19 | } 20 | 21 | .profile-header { 22 | font-size: 2rem; 23 | margin: 1rem; 24 | padding-bottom: .8rem; 25 | font-weight: 400; 26 | color: var(--brand-color); 27 | } 28 | 29 | .user-info-form { 30 | padding: .4rem; 31 | margin-bottom: .2rem; 32 | margin-top: .3rem; 33 | } 34 | 35 | .user-info-form:hover { 36 | border-color: var(--brand-color); 37 | border-radius: 2rem 2rem; 38 | } 39 | 40 | .check-label, .check{ 41 | white-space: nowrap; 42 | } 43 | 44 | .margin-left { 45 | margin-left: 1.8rem; 46 | } 47 | 48 | .user-button { 49 | margin: 1rem 2rem 0 2rem; 50 | padding: .5rem; 51 | border-radius:.5rem; 52 | background-color: var(--brand-color); 53 | } 54 | 55 | .user-button:hover { 56 | background-color: whitesmoke; 57 | } -------------------------------------------------------------------------------- /public/images/dog.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifElseCode/projectBlog/05e5e946281826d8d9c359ba485a88081c443bb3/public/images/dog.jpg -------------------------------------------------------------------------------- /public/js/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifElseCode/projectBlog/05e5e946281826d8d9c359ba485a88081c443bb3/public/js/.gitkeep -------------------------------------------------------------------------------- /public/js/blogs-create.js: -------------------------------------------------------------------------------- 1 | const form = document.querySelector('form'); 2 | 3 | form.addEventListener('submit', async (e) => { 4 | e.preventDefault(); 5 | 6 | const title = form.title.value; 7 | const author = form.dataset.doc; 8 | const content = form.content.value; 9 | 10 | try { 11 | fetch('/dashboard/blogs/create', { 12 | method: 'POST', 13 | body: JSON.stringify({ title, author, content }), 14 | headers: { 'Content-Type': 'application/json' }, 15 | }); 16 | location.assign('/dashboard/blogs'); 17 | } catch (err) { 18 | console.log(err); 19 | } 20 | }); 21 | -------------------------------------------------------------------------------- /public/js/delete-blog.js: -------------------------------------------------------------------------------- 1 | const deleteButtons = document.querySelectorAll("form"); 2 | 3 | deleteButtons.forEach(button => { 4 | button.addEventListener("click", (e) => { 5 | e.preventDefault(); 6 | const id = button.dataset.doc; 7 | const endpoint = `/dashboard/blogs/${id}`; 8 | fetch(endpoint, { 9 | method: "DELETE" 10 | }) 11 | .then(response => window.location.reload()) 12 | .catch(err => console.log(err)); 13 | }); 14 | }); -------------------------------------------------------------------------------- /public/js/login.js: -------------------------------------------------------------------------------- 1 | const form = document.querySelector("form"); 2 | 3 | const emailErrors = document.querySelector(".emailErrors"); 4 | const passwordErrors = document.querySelector(".passwordErrors"); 5 | 6 | form.addEventListener("submit", async (e) => { 7 | e.preventDefault(); 8 | 9 | emailErrors.textContent = ""; 10 | passwordErrors.textContent = ""; 11 | 12 | const email = form.email.value; 13 | const password = form.password.value; 14 | 15 | try { 16 | const res = await fetch("/login", { 17 | method: "POST", 18 | body: JSON.stringify({ email, password }), 19 | headers: { "Content-Type": "application/json" } 20 | }); 21 | const data = await res.json(); 22 | console.log(data); 23 | if (data.errors) { 24 | emailErrors.textContent = data.errors.email; 25 | passwordErrors.textContent = data.errors.password; 26 | } 27 | if (data.user) { 28 | location.assign("/"); 29 | } 30 | } 31 | catch (err) { 32 | console.log(err); 33 | } 34 | }); -------------------------------------------------------------------------------- /public/js/main.js: -------------------------------------------------------------------------------- 1 | // Hamburger menu 2 | const hamburger = document.querySelector('.main-nav--burger'); 3 | const navbarLinks = document.querySelector('.main-nav--links'); 4 | hamburger.addEventListener('click', () => { 5 | navbarLinks.classList.toggle('active'); 6 | }); 7 | -------------------------------------------------------------------------------- /public/js/signup.js: -------------------------------------------------------------------------------- 1 | const form = document.querySelector("form"); 2 | 3 | const usernameErrors = document.querySelector(".usernameErrors"); 4 | const emailErrors = document.querySelector(".emailErrors"); 5 | const passwordErrors = document.querySelector(".passwordErrors"); 6 | 7 | form.addEventListener("submit", async (e) => { 8 | e.preventDefault(); 9 | 10 | usernameErrors.textContent = ""; 11 | emailErrors.textContent = ""; 12 | passwordErrors.textContent = ""; 13 | 14 | const username = form.username.value; 15 | const email = form.email.value; 16 | const password = form.password.value; 17 | 18 | try { 19 | const res = await fetch("/signup", { 20 | method: "POST", 21 | body: JSON.stringify({ username, email, password }), 22 | headers: { "Content-Type": "application/json" } 23 | }); 24 | const data = await res.json(); 25 | if (data.errors) { 26 | usernameErrors.textContent = data.errors.username; 27 | emailErrors.textContent = data.errors.email; 28 | passwordErrors.textContent = data.errors.password; 29 | } 30 | if (data) { 31 | location.assign(data.redirect); 32 | } 33 | } 34 | catch (err) { 35 | console.log(err); 36 | } 37 | }); -------------------------------------------------------------------------------- /public/js/users.js: -------------------------------------------------------------------------------- 1 | const deleteButton = document.querySelectorAll(".delete"); 2 | const adminButtons = document.querySelectorAll(".admin"); 3 | const authorButtons = document.querySelectorAll(".author"); 4 | 5 | deleteButton.forEach(button => { 6 | button.addEventListener("click", (e) => { 7 | e.preventDefault(); 8 | const id = button.dataset.doc; 9 | const endpoint = `/dashboard/users/${id}`; 10 | fetch(endpoint, { 11 | method: "DELETE" 12 | }) 13 | .then(response => window.location.reload()) 14 | .catch(err => console.log(err)); 15 | }); 16 | }) 17 | 18 | adminButtons.forEach(button => { 19 | button.addEventListener("click", (e) => { 20 | const id = button.dataset.doc; 21 | const endpoint = `/dashboard/users/admin/${id}`; 22 | fetch(endpoint, { 23 | method: "PATCH" 24 | }) 25 | .then(response => window.location.reload()) 26 | .catch(err => console.log(err)); 27 | }); 28 | }); 29 | 30 | authorButtons.forEach(button => { 31 | button.addEventListener("click", (e) => { 32 | const id = button.dataset.doc; 33 | const endpoint = `/dashboard/users/author/${id}`; 34 | fetch(endpoint, { 35 | method: "PATCH" 36 | }) 37 | .then(response => window.location.reload()) 38 | .catch(err => console.log(err)); 39 | }); 40 | }); -------------------------------------------------------------------------------- /public/pictures/codingjourney.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifElseCode/projectBlog/05e5e946281826d8d9c359ba485a88081c443bb3/public/pictures/codingjourney.png -------------------------------------------------------------------------------- /public/pictures/denoob.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifElseCode/projectBlog/05e5e946281826d8d9c359ba485a88081c443bb3/public/pictures/denoob.jpg -------------------------------------------------------------------------------- /public/pictures/firstgame.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifElseCode/projectBlog/05e5e946281826d8d9c359ba485a88081c443bb3/public/pictures/firstgame.jpg -------------------------------------------------------------------------------- /public/pictures/kandie.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifElseCode/projectBlog/05e5e946281826d8d9c359ba485a88081c443bb3/public/pictures/kandie.jpg -------------------------------------------------------------------------------- /public/pictures/kratos.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifElseCode/projectBlog/05e5e946281826d8d9c359ba485a88081c443bb3/public/pictures/kratos.jpg -------------------------------------------------------------------------------- /public/pictures/murfy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifElseCode/projectBlog/05e5e946281826d8d9c359ba485a88081c443bb3/public/pictures/murfy.jpg -------------------------------------------------------------------------------- /public/pictures/ormzy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifElseCode/projectBlog/05e5e946281826d8d9c359ba485a88081c443bb3/public/pictures/ormzy.jpg -------------------------------------------------------------------------------- /public/pictures/stephen.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifElseCode/projectBlog/05e5e946281826d8d9c359ba485a88081c443bb3/public/pictures/stephen.jpg -------------------------------------------------------------------------------- /public/pictures/traplocs.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifElseCode/projectBlog/05e5e946281826d8d9c359ba485a88081c443bb3/public/pictures/traplocs.jpg -------------------------------------------------------------------------------- /routes/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifElseCode/projectBlog/05e5e946281826d8d9c359ba485a88081c443bb3/routes/.gitkeep -------------------------------------------------------------------------------- /routes/aboutRouter.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const aboutRouter = express.Router(); 3 | const aboutController = require("../controllers/aboutController"); 4 | 5 | aboutRouter.get("/", aboutController.about_get); 6 | 7 | module.exports = aboutRouter; -------------------------------------------------------------------------------- /routes/authRouter.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const authRouter = express.Router(); 3 | const authController = require("../controllers/authController"); 4 | 5 | authRouter.get("/signup", authController.signup_get); 6 | authRouter.post("/signup", authController.signup_post); 7 | authRouter.get("/signup/confirm/:id", authController.signup_confirm_get); 8 | authRouter.get("/signup/:uniqueNumber", authController.signup_uniqueNumber_get); 9 | authRouter.get("/login", authController.login_get); 10 | authRouter.post("/login", authController.login_post); 11 | authRouter.get("/logout", authController.logout_get); 12 | 13 | module.exports = authRouter; -------------------------------------------------------------------------------- /routes/blogRouter.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const blogRouter = express.Router(); 3 | const blogController = require("../controllers/blogController"); 4 | 5 | blogRouter.get("/", blogController.blogs_get); 6 | blogRouter.get("/:id", blogController.blog_get); 7 | blogRouter.patch("/like/:id", blogController.blog_like_patch); 8 | 9 | 10 | 11 | module.exports = blogRouter; -------------------------------------------------------------------------------- /routes/dashboardRouter.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const dashboardRouter = express.Router(); 3 | const dashboardController = require("../controllers/dashboardController"); 4 | 5 | dashboardRouter.get("/", dashboardController.dashboard_get); 6 | dashboardRouter.get("/users", dashboardController.dashboard_users_get); 7 | dashboardRouter.delete("/users/:id", dashboardController.dashboard_users_delete); 8 | dashboardRouter.patch("/users/admin/:id", dashboardController.dashboard_users_admin_patch); 9 | dashboardRouter.patch("/users/author/:id", dashboardController.dashboard_users_author_patch); 10 | dashboardRouter.get("/blogs", dashboardController.dashboard_blogs_get); 11 | dashboardRouter.get("/blogs/create", dashboardController.dashboard_blogs_create_get); 12 | dashboardRouter.post("/blogs/create", dashboardController.dashboard_blogs_create_post); 13 | dashboardRouter.delete("/blogs/:id", dashboardController.dashboard_blogs_delete); 14 | dashboardRouter.get("/edit-blog/:id", dashboardController.dashboard_blogs_edit); 15 | dashboardRouter.post("/edit-blog/:id/edit", dashboardController.dashboard_blogs_patch); 16 | 17 | module.exports = dashboardRouter; -------------------------------------------------------------------------------- /views/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifElseCode/projectBlog/05e5e946281826d8d9c359ba485a88081c443bb3/views/.DS_Store -------------------------------------------------------------------------------- /views/404.ejs: -------------------------------------------------------------------------------- 1 |
2 | cartoon dog 3 |
Oops!
4 |
404 - Page Not Found
5 |
6 | The page you are looking for might have been removed, had its name 7 | changed, or is temporarly unavailable 8 |
9 | Go To Homepage 10 |
11 | -------------------------------------------------------------------------------- /views/about/about.ejs: -------------------------------------------------------------------------------- 1 |
2 |
3 |

ifElse About

4 |
5 |
6 | 7 |
8 |
9 | 10 |
11 | 12 | 13 |

ABOUT US:

14 | 15 |

We are a group of students and professionals who are all connected to each other by the love and passion of Web Development!

16 | 17 |

We decided to create this blog to showcase our current skills and learn a lot about Web Devolpment in the process. Many people have contributed to this project, some of whom have basic knowledge of HTML and CSS, while others are more knowledgable in the more advanced topics of Devolpment and have watched over us throughout this journey. We hope you are able to enjoy the blog as much as we did in creating it. Below is a list of all the contributors to this blog, many thanks to each and everyone of them who took their time and effort to bring this blog to life! Happy Coding!!

18 | 19 |
20 | 21 |
22 |

Murfy:

23 |

Project Owner and Creator, Team Mentor

24 |
25 | Waiting for image. 26 |

My name is Murfy, commander of the ifElseCode army, General of the Murfy Legions and loyal servant to the true emperor, the Wife. Father to a mad house of 4 kids. Husband to a beautiful wife. And I will have my vengeance, in this life or the next.

27 |
28 | 29 |
30 | 31 |
32 |

Ormzy:

33 |

Project Administrator, Front-End Login Page, Sign-Up Page, 404 Page

34 |
35 | Waiting for image. 36 |

My name is Chris and I'm from the UK. My focus is mainly HTML and CSS but starting to dip into JavaScript. My main career is as a Systems Accountant whereby I found my love for systems and decided to study Web Development alongside doing a degree in Computing & IT with the Open University. In my spare time I'm an avid football fan playing for a local team mainly as a winger.

37 |
38 | 39 |
40 | 41 |
42 |

Stephen Scholtz:

43 |

Back-End Code and projectBlog Database Creator

44 |
45 | Waiting for image. 46 |

Stephen has been heading up the back-end for projectBlog and has been primarily using Node, Express and MongoDB (Mongoose). He enjoys jamming guitar and drums, chatting about philosophy and theology with friends and watching good movies.

47 |
48 | 49 |
50 | 51 |
52 |

First Game:

53 |

Collaborated on the Back-End with Stephen Scholtz

54 |
55 | Waiting for image. 56 |

I am originally from Brazil, but I live in LA, CA. My hobbies include: getting my butt kicked trying to learn Muay Thai and Brazilian Jiu Jitsu while attempting to pay rent by working in a kitchen. I have a pet hamster named Tequila and survive on coffee and ice cream, seriously. And now I have a new found obsession: Coding.

57 |
58 | 59 |
60 | 61 |
62 |

DeNoob:

63 |

Front-End Project Team Leader

64 |
65 | Waiting for image. 66 |

I am currently pursuing a Bachelor of Science in Computer Science at The University of Texas at El Paso (UTEP). Currently, I am classified a junior and my expected graduation date is May 2022. I am a technology enthusiast who strives to develop high-tech products that will make an impact in peoples lives for the better.

67 |
68 | 69 |
70 | 71 |
72 |

Kandie:

73 |

Worked on the Contact Us Page

74 |
75 | Waiting for image. 76 |

I am Tesia, my friends call me Kandie. I am an aspiring Front-End Web Developer. I have a passion for learning and sharing knowledge. In my spare time I like to read, write, play video games, watch horror movies and laugh.

77 |
78 | 79 |
80 | 81 |
82 |

TrapLocs:

83 |

Worked on the Front-End

84 |
85 | Waiting for image. 86 |

I enjoy meeting new people and learning new things. 87 | I understand we all come from different stories, different backgrounds, therefore I try not to judge. My way of doing things may be totally different from your way of doing things. To my fellow friends of the web, this is an invitation to be connected virtually. If you choose not to accept my request, as awkward as it may seem, I don't mind and that's your choice.

88 |
89 | 90 |
91 | 92 |
93 |

Ruan Potgieter:

94 |

Worked on the About Us Page

95 |
96 | Waiting for image. 97 |

My name is Ruan Potgieter and I currently work as a Draughtsman in Cape Town, South Africa. I enjoy playing video games, reading Non-Fiction books and just overall being a pretty chill guy. Keep delicious food away or I will attempt a sampling.

98 |
99 | 100 |
101 | 102 |

Special Thanks:

103 | 104 |
105 | 106 |

Special thanks for The Coding Journey, who has been our teacher throughout the entire process and has been vital in this project. He has a YouTube account which can be viewed here.

107 | 108 |
109 |

The Coding Journey:

110 | 111 |
112 | 113 | Waiting for image. 114 |

I am a Romanian Front-End Developer living in Nottingham, UK. My current hobbies are coding (yeah I know what a nerd ha ha), magic, chess and teaching people. I also enjoy humour a lot. This is my GitHub account.

115 |
116 | -------------------------------------------------------------------------------- /views/auth/login.ejs: -------------------------------------------------------------------------------- 1 |
2 |

Welcome back!

3 |
4 | 10 |
11 |
12 |
13 | 19 |
20 |
21 |
22 | 23 | 24 |
25 | 26 |
27 | 28 |
29 |
30 | 31 | 32 | -------------------------------------------------------------------------------- /views/auth/signup.ejs: -------------------------------------------------------------------------------- 1 |
2 |

Let's get you on board!

3 |
4 | 10 |
11 |
12 |
13 | 19 |
20 |
21 |
22 | 29 |
30 |
31 |
32 | 33 |
34 |
35 | 36 | -------------------------------------------------------------------------------- /views/blog/blog.ejs: -------------------------------------------------------------------------------- 1 |
2 |
3 | An early logo of ifElseCode 8 |

<%= blog.title %>

9 |
10 | <%= `${blog.date.getDate()} ${months[blog.date.getMonth()]} 11 | ${blog.date.getFullYear()}` %> / by 12 | <%= blog.author %> 13 |
14 |
<%- blog.content %>
15 | 16 | <% if (user) { %> 17 | <% if (blog.likes.includes(user.email)) { %> 18 |

Liked

19 | <% } else { %> 20 | 21 | <% } %> 22 | <% } else { %> 23 |

Log in to like this blog

24 | <% } %> 25 | 26 |

Likes: <%= blog.likes.length %>

27 |
28 | 29 | 30 |
31 | -------------------------------------------------------------------------------- /views/blog/blogs.ejs: -------------------------------------------------------------------------------- 1 |
2 |

ifElse Top Blogs

3 | 4 |
5 |
6 | <% blogs.map(blog => { %> 7 |
8 | An early logo of ifElseCode 13 |

<%= blog.title %>

14 |
15 | <%= `${blog.date.getDate()} ${months[blog.date.getMonth()]} 16 | ${blog.date.getFullYear()}` %> / by 17 | <%= blog.author %> 18 |
19 |
20 | <%- blog.content.substring(0, 450) %>... 21 |
22 | Read More 23 |
24 | <% }) %> 25 |
26 | -------------------------------------------------------------------------------- /views/contact.ejs: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 |

ifElse Contact

6 | 7 |
8 |
9 |

Why not drop us a line?

10 | 18 |
19 |
20 |
21 |
22 | 23 | 24 | 25 | 26 |
27 |
28 |
29 |
30 | -------------------------------------------------------------------------------- /views/dashboard/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifElseCode/projectBlog/05e5e946281826d8d9c359ba485a88081c443bb3/views/dashboard/.gitkeep -------------------------------------------------------------------------------- /views/dashboard/blogs-create.ejs: -------------------------------------------------------------------------------- 1 |
2 |

Create New Blog Post

3 | 4 | 5 | Go Back 6 | 7 |
8 |
9 | 10 | 17 | 18 |
19 | 20 | 21 | -------------------------------------------------------------------------------- /views/dashboard/blogs.ejs: -------------------------------------------------------------------------------- 1 |
2 |

3 | Blog Posts All blog posts in the database 4 |

5 | 6 | 7 | Add Blog 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | <% blogs.forEach(blog=> { %> 19 | 20 | 23 | 26 | 29 | 36 | 37 | <% }) %> 38 |
DateTitleAuthorActions
21 | <%= `${blog.date.getDate()} ${months[blog.date.getMonth()]} ${blog.date.getFullYear()}` %> 22 | 24 | <%= blog.title %> 25 | 27 | <%= blog.author %> 28 | 30 | Edit 32 |
33 | Delete 34 |
35 |
39 | 40 | -------------------------------------------------------------------------------- /views/dashboard/edit-blog.ejs: -------------------------------------------------------------------------------- 1 |
2 |

Create New Blog Post

3 | 4 | 5 | Go Back 6 | 7 |
8 |
9 | 10 | 12 | 14 | 15 |
16 | 17 | -------------------------------------------------------------------------------- /views/dashboard/index.ejs: -------------------------------------------------------------------------------- 1 |

Main Admin Page

-------------------------------------------------------------------------------- /views/dashboard/user-profile.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 |

Welcome to ifElseCode!

6 |
7 |
8 |

-User Profile-

9 |
10 |
11 | 12 |
13 | 14 |
15 |
16 |
17 | 18 |
19 | 20 |
21 |
22 |
23 | 24 |
25 | 26 |
27 |
28 |
29 | 30 |
31 | 32 |
33 |
34 |
35 | 36 | 37 | 38 | 39 |
40 | 41 | 42 |
43 |
44 |
45 | 46 | -------------------------------------------------------------------------------- /views/dashboard/users.ejs: -------------------------------------------------------------------------------- 1 |
2 |

3 | Users All users in the database 4 |

5 |
6 | 7 | <% if (user.role.admin) { %> 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | <% users.forEach(user =>{ %> 19 | 20 | 21 | 22 | 23 | 28 | 29 | <% }) %> 30 |
UsernameEmailAdminActions
<%= user.username %><%= user.email %><%= user.role.admin ? 'Admin' : 'Author' %> 24 | Make Admin 25 | Make Author 26 | Delete 27 |
31 | <% } %> 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /views/email-confirm.ejs: -------------------------------------------------------------------------------- 1 |

Email sent

2 |

An email was sent to <%= email %>

-------------------------------------------------------------------------------- /views/emails/confirm.ejs: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 |
16 |

Dear <%= username %>,

17 |

Confirm your email below

18 |

This email was sent from the projectBlog website when you signed up

19 |

Click the button below to confirm your email address:

20 | 21 |
-------------------------------------------------------------------------------- /views/home.ejs: -------------------------------------------------------------------------------- 1 |
2 |

ifElse Top Blogs

3 | 4 |
5 | 6 |
7 | 8 | <% blogs.slice(0, 5).map(blog => { %> 9 | 10 |
11 | An early logo of ifElseCode 16 |

<%= blog.title %>

17 |
18 | <%= `${blog.date.getDate()} ${months[blog.date.getMonth()]} 19 | ${blog.date.getFullYear()}` %> / by 20 | <%= blog.author %> 21 |
22 |
23 | <%- blog.content.substring(0, 450) %>... 24 |
25 | Read More 26 |
27 | 28 | <% }) %> 29 | 30 |
31 | -------------------------------------------------------------------------------- /views/layouts/dashboard-layout.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 16 | ifElseCode 17 | 18 | 19 | 20 |
21 |
22 |
23 | <%= user.username.split(" ").map(name => name[0]).join("") %> 24 |
25 |
26 |
<%= user.username %>
27 | <% if (user.role.admin) { %> 28 |
Admin
29 | <% } else if (user.role.author) { %> 30 |
Author
31 | <% } else { %> 32 |
Visitor
33 | <% } %> 34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | 73 |
74 |
75 | <%- body %> 76 |
77 | 78 | 79 | -------------------------------------------------------------------------------- /views/layouts/main-layout.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 18 | 19 | 20 | ifElseCode 21 | 22 | 23 | 24 | 69 | 70 | 71 |
<%- body %>
72 | 73 |
74 | 79 |
@ Copyright 2020 ifElseCode. All rights reserved
80 |
81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /views/partials/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifElseCode/projectBlog/05e5e946281826d8d9c359ba485a88081c443bb3/views/partials/.gitkeep -------------------------------------------------------------------------------- /views/partials/footer.ejs: -------------------------------------------------------------------------------- 1 |

Footer

-------------------------------------------------------------------------------- /views/partials/header.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | ifElseCode 10 | 11 | 12 | 13 | 26 | -------------------------------------------------------------------------------- /views/partials/section-left.ejs: -------------------------------------------------------------------------------- 1 |
2 | 3 |
-------------------------------------------------------------------------------- /views/partials/section-right.ejs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifElseCode/projectBlog/05e5e946281826d8d9c359ba485a88081c443bb3/views/partials/section-right.ejs --------------------------------------------------------------------------------