├── backend
├── accounts
│ ├── __init__.py
│ ├── models.py
│ ├── tests.py
│ ├── admin.py
│ ├── apps.py
│ ├── urls.py
│ ├── serializers.py
│ └── views.py
├── blog
│ ├── __init__.py
│ ├── asgi.py
│ ├── wsgi.py
│ ├── urls.py
│ └── settings.py
├── posts
│ ├── __init__.py
│ ├── migrations
│ │ ├── __init__.py
│ │ └── 0001_initial.py
│ ├── forms.py
│ ├── tests.py
│ ├── apps.py
│ ├── admin.py
│ ├── pagination.py
│ ├── urls.py
│ ├── mixins.py
│ ├── permissions.py
│ ├── models.py
│ ├── serializers.py
│ └── views.py
├── media
│ └── media
│ │ └── 2.jpg
├── requirements.txt
├── manage.py
└── README.md
├── frontend
└── blog-app
│ ├── src
│ ├── Containers
│ │ ├── Authentication
│ │ │ ├── Logout
│ │ │ │ ├── Logout.module.css
│ │ │ │ └── Logout.js
│ │ │ ├── Signup
│ │ │ │ ├── Signup.module.css
│ │ │ │ └── Signup.js
│ │ │ ├── ForgotPass
│ │ │ │ ├── ForgotPass.module.css
│ │ │ │ └── ForgotPass.js
│ │ │ ├── Signin
│ │ │ │ ├── Signin.module.css
│ │ │ │ └── Signin.js
│ │ │ ├── ResetPass
│ │ │ │ └── ResetPass.js
│ │ │ └── Auth.module.css
│ │ ├── Home
│ │ │ ├── Home.module.css
│ │ │ └── Home.js
│ │ └── Profile
│ │ │ ├── Profile.module.css
│ │ │ └── Profile.js
│ ├── Components
│ │ ├── Content
│ │ │ ├── Content.module.css
│ │ │ └── Content.js
│ │ └── UI
│ │ │ ├── Backdrop
│ │ │ ├── Backdrop.module.css
│ │ │ └── Backdrop.js
│ │ │ ├── Toolbar
│ │ │ ├── Navigation
│ │ │ │ ├── Navigation.module.css
│ │ │ │ ├── NavigationItems
│ │ │ │ │ ├── NavigationItems.module.css
│ │ │ │ │ ├── NavigationItem
│ │ │ │ │ │ ├── NavigationItem.js
│ │ │ │ │ │ └── NavigationItem.module.css
│ │ │ │ │ └── NavigationItems.js
│ │ │ │ └── Navigation.js
│ │ │ ├── Toolbar.module.css
│ │ │ ├── HamBurger
│ │ │ │ ├── HamBurger.js
│ │ │ │ └── HamBurger.module.css
│ │ │ └── Toolbar.js
│ │ │ ├── SideDrawer
│ │ │ ├── SideDrawer.module.css
│ │ │ └── SideDrawer.js
│ │ │ ├── PageNotFound
│ │ │ ├── PageNotFound.module.css
│ │ │ └── PageNotFound.js
│ │ │ ├── PasswordShowHide
│ │ │ ├── PasswordShowHide.module.css
│ │ │ └── PasswordShowHide.js
│ │ │ └── Footer
│ │ │ ├── Footer.module.css
│ │ │ └── Footer.js
│ ├── hoc
│ │ └── Wrap
│ │ │ └── Wrap.js
│ ├── App.css
│ ├── setupTests.js
│ ├── App.test.js
│ ├── index.css
│ ├── reportWebVitals.js
│ ├── index.js
│ ├── App.js
│ └── logo.svg
│ ├── public
│ ├── robots.txt
│ ├── logo.png
│ ├── favicon.ico
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── apple-touch-icon.png
│ ├── android-chrome-192x192.png
│ ├── android-chrome-512x512.png
│ ├── site.webmanifest
│ ├── manifest.json
│ └── index.html
│ ├── .gitignore
│ ├── package.json
│ └── README.md
├── .vscode
└── settings.json
├── README.md
└── .gitignore
/backend/accounts/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/backend/blog/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/backend/posts/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/backend/posts/migrations/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/backend/posts/forms.py:
--------------------------------------------------------------------------------
1 | from django import forms
--------------------------------------------------------------------------------
/backend/accounts/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
--------------------------------------------------------------------------------
/frontend/blog-app/src/Containers/Authentication/Logout/Logout.module.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/blog-app/src/Containers/Authentication/Signup/Signup.module.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "python.formatting.provider": "black"
3 | }
--------------------------------------------------------------------------------
/frontend/blog-app/src/Containers/Authentication/ForgotPass/ForgotPass.module.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/backend/accounts/tests.py:
--------------------------------------------------------------------------------
1 | from django.test import TestCase
2 |
3 | # Create your tests here.
4 |
--------------------------------------------------------------------------------
/backend/posts/tests.py:
--------------------------------------------------------------------------------
1 | from django.test import TestCase
2 |
3 | # Create your tests here.
4 |
--------------------------------------------------------------------------------
/backend/accounts/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 |
3 | # Register your models here.
4 |
--------------------------------------------------------------------------------
/backend/media/media/2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ExpressHermes/Blog-API/HEAD/backend/media/media/2.jpg
--------------------------------------------------------------------------------
/frontend/blog-app/src/Components/Content/Content.module.css:
--------------------------------------------------------------------------------
1 | .content{
2 | min-height: calc(100vh - 120px);
3 | }
--------------------------------------------------------------------------------
/frontend/blog-app/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/backend/posts/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class PostsConfig(AppConfig):
5 | name = 'posts'
6 |
--------------------------------------------------------------------------------
/frontend/blog-app/public/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ExpressHermes/Blog-API/HEAD/frontend/blog-app/public/logo.png
--------------------------------------------------------------------------------
/frontend/blog-app/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ExpressHermes/Blog-API/HEAD/frontend/blog-app/public/favicon.ico
--------------------------------------------------------------------------------
/frontend/blog-app/src/hoc/Wrap/Wrap.js:
--------------------------------------------------------------------------------
1 | const Wrap = (props) => {
2 | return(props.children)
3 | }
4 |
5 | export default Wrap;
--------------------------------------------------------------------------------
/backend/accounts/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class AccountsConfig(AppConfig):
5 | name = "accounts"
6 |
--------------------------------------------------------------------------------
/frontend/blog-app/public/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ExpressHermes/Blog-API/HEAD/frontend/blog-app/public/favicon-16x16.png
--------------------------------------------------------------------------------
/frontend/blog-app/public/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ExpressHermes/Blog-API/HEAD/frontend/blog-app/public/favicon-32x32.png
--------------------------------------------------------------------------------
/frontend/blog-app/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ExpressHermes/Blog-API/HEAD/frontend/blog-app/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/backend/posts/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from .models import Post
3 |
4 | # Register your models here.
5 | admin.site.register(Post)
6 |
--------------------------------------------------------------------------------
/frontend/blog-app/public/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ExpressHermes/Blog-API/HEAD/frontend/blog-app/public/android-chrome-192x192.png
--------------------------------------------------------------------------------
/frontend/blog-app/public/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ExpressHermes/Blog-API/HEAD/frontend/blog-app/public/android-chrome-512x512.png
--------------------------------------------------------------------------------
/frontend/blog-app/src/Containers/Home/Home.module.css:
--------------------------------------------------------------------------------
1 | .helperText{
2 | padding: 20px;
3 | font-family: monospace;
4 | margin: 20px;
5 | background-color: #ddd;
6 | }
--------------------------------------------------------------------------------
/frontend/blog-app/src/App.css:
--------------------------------------------------------------------------------
1 | *{
2 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
3 | }
--------------------------------------------------------------------------------
/backend/posts/pagination.py:
--------------------------------------------------------------------------------
1 | from rest_framework.pagination import (
2 | LimitOffsetPagination,
3 | )
4 |
5 | class PostLimitOffsetPagination(LimitOffsetPagination):
6 | default_limit = 10
7 | max_limit = 10
--------------------------------------------------------------------------------
/frontend/blog-app/src/Components/UI/Backdrop/Backdrop.module.css:
--------------------------------------------------------------------------------
1 | .BackDrop{
2 | width: 100%;
3 | z-index:900;
4 | height: 100vh;
5 | background-color: #44444480;
6 | position: fixed;
7 | top: 0;
8 | left: 0;
9 | }
--------------------------------------------------------------------------------
/frontend/blog-app/src/Containers/Authentication/Logout/Logout.js:
--------------------------------------------------------------------------------
1 | import {Redirect} from 'react-router-dom';
2 |
3 | const logout = () => {
4 | return(
5 |
6 | )
7 | }
8 |
9 | export default logout;
--------------------------------------------------------------------------------
/frontend/blog-app/src/Components/UI/Toolbar/Navigation/Navigation.module.css:
--------------------------------------------------------------------------------
1 | .navBar{
2 | width: fit-content;
3 | float: right;
4 | }
5 |
6 | @media screen and (max-width: 900px){
7 | .navBar{
8 | display: none;
9 | }
10 | }
--------------------------------------------------------------------------------
/frontend/blog-app/src/Components/UI/SideDrawer/SideDrawer.module.css:
--------------------------------------------------------------------------------
1 | .SideDrawer{
2 | position: fixed;
3 | right: 0;
4 | top: 0;
5 | height: 100vh;
6 | width: 80%;
7 | max-width: 500px;
8 | z-index: 1000;
9 | transition: 0.4s all ease-in-out;
10 | }
--------------------------------------------------------------------------------
/frontend/blog-app/src/Components/UI/Toolbar/Navigation/NavigationItems/NavigationItems.module.css:
--------------------------------------------------------------------------------
1 | @media screen and (max-width:900px) {
2 | .NavigationItems {
3 | padding: 80px 0;
4 | background-color: #fff;
5 | height: 100%;
6 | width: 100%;
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/frontend/blog-app/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/frontend/blog-app/public/site.webmanifest:
--------------------------------------------------------------------------------
1 | {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
--------------------------------------------------------------------------------
/frontend/blog-app/src/App.test.js:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import App from './App';
3 |
4 | test('renders learn react link', () => {
5 | render( );
6 | const linkElement = screen.getByText(/learn react/i);
7 | expect(linkElement).toBeInTheDocument();
8 | });
9 |
--------------------------------------------------------------------------------
/frontend/blog-app/.gitignore:
--------------------------------------------------------------------------------
1 | # dependencies
2 | /node_modules
3 | /.pnp
4 | .pnp.js
5 |
6 | # Frontend
7 | # testing
8 | /coverage
9 |
10 | # production
11 | /build
12 |
13 | # misc
14 | .DS_Store
15 | .env.local
16 | .env.development.local
17 | .env.test.local
18 | .env.production.local
19 | .eslintcache
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
--------------------------------------------------------------------------------
/frontend/blog-app/src/Components/UI/Backdrop/Backdrop.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import classes from './Backdrop.module.css';
4 |
5 | const backDrop = (props) => {
6 | return (
7 |
12 |
)
13 | }
14 |
15 | export default backDrop;
--------------------------------------------------------------------------------
/backend/accounts/urls.py:
--------------------------------------------------------------------------------
1 | from django.urls import path
2 | from .views import UserCreateAPIView, UserListAPIView, UserDetailAPIView
3 |
4 | app_name = "accounts"
5 |
6 | urlpatterns = [
7 | path("", UserListAPIView.as_view(), name="user_detail"),
8 | path("register/", UserCreateAPIView.as_view(), name="user_create"),
9 | path("/", UserDetailAPIView.as_view(), name="user_detail"),
10 | ]
--------------------------------------------------------------------------------
/frontend/blog-app/src/Components/UI/Toolbar/Toolbar.module.css:
--------------------------------------------------------------------------------
1 | .toolBar{
2 | height: 70px;
3 | padding: 3px 20px;
4 | box-sizing: border-box;
5 | background-color:rgb(138, 23, 80);
6 | color: #eee;
7 | }
8 |
9 | .logo{
10 | height: calc(100%);
11 | display: inline-block;
12 | width: fit-content;
13 | margin: 0 0;
14 | /* border: 2px solid white; */
15 | }
16 |
17 | .logo img{
18 | height: 100%;
19 | }
20 |
--------------------------------------------------------------------------------
/frontend/blog-app/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/frontend/blog-app/src/reportWebVitals.js:
--------------------------------------------------------------------------------
1 | const reportWebVitals = onPerfEntry => {
2 | if (onPerfEntry && onPerfEntry instanceof Function) {
3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4 | getCLS(onPerfEntry);
5 | getFID(onPerfEntry);
6 | getFCP(onPerfEntry);
7 | getLCP(onPerfEntry);
8 | getTTFB(onPerfEntry);
9 | });
10 | }
11 | };
12 |
13 | export default reportWebVitals;
14 |
--------------------------------------------------------------------------------
/backend/blog/asgi.py:
--------------------------------------------------------------------------------
1 | """
2 | ASGI config for blog project.
3 |
4 | It exposes the ASGI callable as a module-level variable named ``application``.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/3.1/howto/deployment/asgi/
8 | """
9 |
10 | import os
11 |
12 | from django.core.asgi import get_asgi_application
13 |
14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "blog.settings")
15 |
16 | application = get_asgi_application()
17 |
--------------------------------------------------------------------------------
/backend/blog/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for blog project.
3 |
4 | It exposes the WSGI callable as a module-level variable named ``application``.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/3.1/howto/deployment/wsgi/
8 | """
9 |
10 | import os
11 |
12 | from django.core.wsgi import get_wsgi_application
13 |
14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "blog.settings")
15 |
16 | application = get_wsgi_application()
17 |
--------------------------------------------------------------------------------
/frontend/blog-app/src/Components/UI/Toolbar/Navigation/Navigation.js:
--------------------------------------------------------------------------------
1 | import NavigationItems from './NavigationItems/NavigationItems';
2 | import Wrap from '../../../../hoc/Wrap/Wrap';
3 |
4 | import classes from './Navigation.module.css';
5 |
6 | const Navigation = () => {
7 | return(
8 |
9 |
10 | {}}
12 | />
13 |
14 |
15 | )
16 | }
17 |
18 | export default Navigation;
--------------------------------------------------------------------------------
/frontend/blog-app/src/Components/UI/Toolbar/HamBurger/HamBurger.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import classes from './HamBurger.module.css';
4 |
5 | const hamBurger = (props) => {
6 | return (
7 |
12 | );
13 | }
14 |
15 | export default hamBurger;
--------------------------------------------------------------------------------
/frontend/blog-app/src/Components/UI/Toolbar/HamBurger/HamBurger.module.css:
--------------------------------------------------------------------------------
1 | .Burger{
2 | height: 40px;
3 | margin: 13.5px 0;
4 | float: right;
5 | width: 45px;
6 | cursor: pointer;
7 | }
8 |
9 | .Burger:focus{
10 | outline: none;
11 | }
12 |
13 | .bar{
14 | width: 80%;
15 | height: 4px;
16 | margin: 4.2px;
17 | background-color: rgb(219, 219, 219);
18 | border: 1px solid rgb(231, 231, 231);
19 | border-radius: 5px;
20 | }
21 |
22 | @media screen and (min-width:900px){
23 | .Burger{
24 | display: none;
25 | }
26 | }
--------------------------------------------------------------------------------
/frontend/blog-app/src/Components/Content/Content.js:
--------------------------------------------------------------------------------
1 | import Wrap from '../../hoc/Wrap/Wrap';
2 | import Toolbar from '../UI/Toolbar/Toolbar';
3 | import Footer from '../UI/Footer/Footer';
4 |
5 | import classes from './Content.module.css';
6 |
7 | const content = (props) => {
8 | return(
9 |
10 |
11 |
12 | {props.children}
13 |
14 |
15 |
16 | )
17 | }
18 |
19 | export default content;
--------------------------------------------------------------------------------
/frontend/blog-app/src/Components/UI/SideDrawer/SideDrawer.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import classes from "./SideDrawer.module.css";
4 |
5 | import NavigationItems from "../Toolbar/Navigation/NavigationItems/NavigationItems";
6 |
7 | const sideDrawer = (props) => {
8 | return (
9 |
13 |
14 |
15 | );
16 | };
17 |
18 | export default sideDrawer;
19 |
--------------------------------------------------------------------------------
/frontend/blog-app/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "Blog App",
3 | "name": "Blog App",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/frontend/blog-app/src/Components/UI/PageNotFound/PageNotFound.module.css:
--------------------------------------------------------------------------------
1 | .external{
2 | padding: 100px 0;
3 | }
4 |
5 | .notFound{
6 | width: 90%;
7 | background-color: #eee;
8 | margin: 0px auto;
9 | padding: 50px 20px;
10 | box-sizing: border-box;
11 | }
12 |
13 | .qMark{
14 | color: #bbb;
15 | text-align: center;
16 | font-size: 200px;
17 | }
18 |
19 | .notFoundMsg{
20 | color: #777;
21 | text-align: center;
22 | }
23 |
24 | .notFoundLink a{
25 | color: rgb(4, 28, 82);
26 | display: inline-block;
27 | text-decoration: none;
28 | padding: 10px;
29 | }
30 |
31 | .notFoundLink a:hover{
32 | color: rgb(39, 56, 95);
33 | text-decoration: underline;
34 | }
35 |
--------------------------------------------------------------------------------
/frontend/blog-app/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import reportWebVitals from './reportWebVitals';
6 |
7 | import { BrowserRouter } from 'react-router-dom';
8 |
9 | ReactDOM.render(
10 |
11 |
12 |
13 |
14 | ,
15 | document.getElementById('root')
16 | );
17 |
18 | // If you want to start measuring performance in your app, pass a function
19 | // to log results (for example: reportWebVitals(console.log))
20 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
21 | reportWebVitals();
22 |
--------------------------------------------------------------------------------
/frontend/blog-app/src/Components/UI/PasswordShowHide/PasswordShowHide.module.css:
--------------------------------------------------------------------------------
1 | .pass{
2 | position: relative;
3 | }
4 | .input{
5 | background-color: #aaaaaa00;
6 | width: 90%;
7 | padding: 5px;
8 | margin: 15px auto;
9 | height: 32px;
10 | font-size: 18px;
11 | border: none;
12 | border-bottom: 2px solid grey;
13 | display: block;
14 | text-align: left;
15 | color: unset;
16 | }
17 |
18 | .input:focus{
19 | outline: none;
20 | background-color: #e9f1ff;
21 | border-color: rgb(44, 119, 216);
22 | }
23 |
24 | .toggle{
25 | position: absolute;
26 | right: 25px;
27 | top: 0;
28 | height: 100%;
29 | width: 15px;
30 | line-height: 44px;
31 | }
32 |
33 | .toggle img{
34 | width: 100%;
35 |
36 | }
--------------------------------------------------------------------------------
/backend/requirements.txt:
--------------------------------------------------------------------------------
1 | appdirs==1.4.4
2 | asgiref==3.3.0
3 | black==20.8b1
4 | certifi==2020.11.8
5 | chardet==3.0.4
6 | click==7.1.2
7 | coreapi==2.3.3
8 | coreschema==0.0.4
9 | Django==3.1.3
10 | djangorestframework==3.12.2
11 | drf-yasg==1.20.0
12 | idna==2.10
13 | inflection==0.5.1
14 | itypes==1.2.0
15 | Jinja2==2.11.2
16 | MarkupSafe==1.1.1
17 | mypy-extensions==0.4.3
18 | packaging==20.4
19 | pathspec==0.8.1
20 | Pillow==9.0.0
21 | pyparsing==2.4.7
22 | pytz==2020.4
23 | regex==2020.10.28
24 | requests==2.24.0
25 | ruamel.yaml==0.16.12
26 | ruamel.yaml.clib==0.2.2
27 | six==1.15.0
28 | sqlparse==0.4.1
29 | toml==0.10.2
30 | typed-ast==1.4.1
31 | typing-extensions==3.7.4.3
32 | uritemplate==3.0.1
33 | urllib3==1.25.11
34 |
--------------------------------------------------------------------------------
/frontend/blog-app/src/Components/UI/Toolbar/Navigation/NavigationItems/NavigationItem/NavigationItem.js:
--------------------------------------------------------------------------------
1 | import {NavLink} from 'react-router-dom';
2 | import classes from './NavigationItem.module.css';
3 |
4 | const navigationItem = (props) => {
5 | return(
6 | props.hide()}
8 | className={
9 | props.specialClass.length > 0 ?
10 | [classes["link"], classes[props.specialClass]].join(' ') :
11 | classes["link"]
12 | }
13 | to={props.linkTo}
14 | exact={props.exact}
15 | activeClassName={classes["activeLink"]}
16 | style={props.specialStyles}
17 | activeStyle={props.specialActiveStyles}
18 | >
19 | {props.children}
20 |
21 | )
22 | }
23 |
24 | export default navigationItem;
--------------------------------------------------------------------------------
/backend/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | """Django's command-line utility for administrative tasks."""
3 | import os
4 | import sys
5 |
6 |
7 | def main():
8 | """Run administrative tasks."""
9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'blog.settings')
10 | try:
11 | from django.core.management import execute_from_command_line
12 | except ImportError as exc:
13 | raise ImportError(
14 | "Couldn't import Django. Are you sure it's installed and "
15 | "available on your PYTHONPATH environment variable? Did you "
16 | "forget to activate a virtual environment?"
17 | ) from exc
18 | execute_from_command_line(sys.argv)
19 |
20 |
21 | if __name__ == '__main__':
22 | main()
23 |
--------------------------------------------------------------------------------
/frontend/blog-app/src/Components/UI/PageNotFound/PageNotFound.js:
--------------------------------------------------------------------------------
1 | import Wrap from '../../../hoc/Wrap/Wrap';
2 |
3 | import {Link} from 'react-router-dom';
4 | import classes from './PageNotFound.module.css';
5 |
6 | const pagenotfound = () => {
7 | return(
8 |
9 |
10 |
11 |
?
12 |
13 | Looks like you have gone out of way? Use the following links to get back on track or use the Navbar or Footer
14 |
15 | Home
16 |
17 |
18 |
19 |
20 |
21 | )
22 | }
23 |
24 | export default pagenotfound;
--------------------------------------------------------------------------------
/frontend/blog-app/src/Components/UI/Toolbar/Navigation/NavigationItems/NavigationItems.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import classes from "./NavigationItems.module.css";
4 |
5 | import NavigationItem from "./NavigationItem/NavigationItem";
6 |
7 | const navigationItems = (props) => {
8 | return (
9 |
10 | Home
11 | Write a Blog
12 | My Blogs
13 | Sign In
14 |
15 | );
16 | }
17 |
18 | export default navigationItems;
19 |
--------------------------------------------------------------------------------
/backend/posts/urls.py:
--------------------------------------------------------------------------------
1 | from django.urls import path
2 | from .views import (
3 | CreatePostAPIView,
4 | ListPostAPIView,
5 | DetailPostAPIView,
6 | CreateCommentAPIView,
7 | ListCommentAPIView,
8 | DetailCommentAPIView,
9 | )
10 |
11 | app_name = "posts"
12 |
13 | urlpatterns = [
14 | path("", ListPostAPIView.as_view(), name="list_post"),
15 | path("create/", CreatePostAPIView.as_view(), name="create_post"),
16 | path("/", DetailPostAPIView.as_view(), name="post_detail"),
17 | path("/comment/", ListCommentAPIView.as_view(), name="list_comment"),
18 | path(
19 | "/comment/create/",
20 | CreateCommentAPIView.as_view(),
21 | name="create_comment",
22 | ),
23 | path(
24 | "/comment//",
25 | DetailCommentAPIView.as_view(),
26 | name="comment_detail",
27 | ),
28 | ]
--------------------------------------------------------------------------------
/backend/accounts/serializers.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth import get_user_model
2 | from rest_framework import serializers
3 |
4 | User = get_user_model()
5 |
6 |
7 | class UserSerializer(serializers.ModelSerializer):
8 | id = serializers.PrimaryKeyRelatedField(read_only=True)
9 | password = serializers.CharField(min_length=8, max_length=32, write_only=True)
10 | email = serializers.EmailField(max_length=50, allow_blank=False)
11 |
12 | class Meta:
13 | model = User
14 | fields = ["id", "username", "email", "password"]
15 |
16 | def create(self, validated_data):
17 | username = validated_data["username"]
18 | email = validated_data["email"]
19 | password = validated_data["password"]
20 | user_obj = User(username=username, email=email)
21 | user_obj.set_password(password)
22 | user_obj.save()
23 | return user_obj
24 |
--------------------------------------------------------------------------------
/backend/posts/mixins.py:
--------------------------------------------------------------------------------
1 | from django.shortcuts import get_object_or_404
2 | from .models import Post
3 |
4 |
5 | class MultipleFieldLookupMixin:
6 | """
7 | Mixin to filter comments based on slug and id
8 | """
9 |
10 | def get_object(self):
11 | queryset = self.get_queryset() # Get the base queryset
12 | queryset = self.filter_queryset(queryset) # Apply any filter backends
13 | filter = {}
14 | # for field in self.lookup_fields:
15 | # if self.kwargs[field]: # Ignore empty fields.
16 | # filter[field] = self.kwargs[field]
17 | parent_id = Post.objects.get(slug=self.kwargs["slug"]).id
18 | filter["parent"] = parent_id
19 | filter["id"] = self.kwargs["id"]
20 | obj = get_object_or_404(queryset, **filter) # Lookup the object
21 | self.check_object_permissions(self.request, obj)
22 | return obj
--------------------------------------------------------------------------------
/backend/posts/permissions.py:
--------------------------------------------------------------------------------
1 | from rest_framework.permissions import BasePermission, SAFE_METHODS
2 |
3 |
4 | class IsOwner(BasePermission):
5 | """
6 | Custom permission to check if the user is owner of the object.
7 | """
8 | message = "You can not delete another user"
9 |
10 | def has_object_permission(self, request, view, obj):
11 | if request.method in SAFE_METHODS:
12 | return True
13 | print(obj, request.user)
14 | return obj == request.user
15 |
16 |
17 | class IsOwnerOrReadOnly(BasePermission):
18 | """
19 | Custom permission to check if the user is owner of the object.
20 | """
21 | message = "You must be the owner of this object."
22 |
23 | def has_object_permission(self, request, view, obj):
24 | if request.method in SAFE_METHODS:
25 | return True
26 | return obj.author == request.user
27 |
28 |
--------------------------------------------------------------------------------
/frontend/blog-app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "blog-app",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^5.11.4",
7 | "@testing-library/react": "^11.1.0",
8 | "@testing-library/user-event": "^12.1.10",
9 | "react": "^17.0.1",
10 | "react-dom": "^17.0.1",
11 | "react-router": "^5.2.0",
12 | "react-router-dom": "^5.2.0",
13 | "react-scripts": "4.0.1",
14 | "web-vitals": "^0.2.4"
15 | },
16 | "scripts": {
17 | "start": "react-scripts start",
18 | "build": "react-scripts build",
19 | "test": "react-scripts test",
20 | "eject": "react-scripts eject"
21 | },
22 | "eslintConfig": {
23 | "extends": [
24 | "react-app",
25 | "react-app/jest"
26 | ]
27 | },
28 | "browserslist": {
29 | "production": [
30 | ">0.2%",
31 | "not dead",
32 | "not op_mini all"
33 | ],
34 | "development": [
35 | "last 1 chrome version",
36 | "last 1 firefox version",
37 | "last 1 safari version"
38 | ]
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/frontend/blog-app/src/App.js:
--------------------------------------------------------------------------------
1 | import './App.css';
2 |
3 | import { Route, Switch } from "react-router-dom";
4 |
5 | import Content from "./Components/Content/Content";
6 | import Wrap from "./hoc/Wrap/Wrap";
7 | import Home from './Containers/Home/Home';
8 | import Profile from './Containers/Profile/Profile';
9 | import PageNotFound from './Components/UI/PageNotFound/PageNotFound';
10 | import Logout from './Containers/Authentication/Logout/Logout';
11 | import Signin from './Containers/Authentication/Signin/Signin';
12 | import SignUp from './Containers/Authentication/Signup/Signup';
13 | import ForgotPass from './Containers/Authentication/ForgotPass/ForgotPass';
14 | import ResetPass from './Containers/Authentication/ResetPass/ResetPass';
15 |
16 | const app = () => {
17 | return (
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | );
33 | };
34 |
35 | export default app;
--------------------------------------------------------------------------------
/frontend/blog-app/src/Components/UI/PasswordShowHide/PasswordShowHide.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import Wrap from '../../../hoc/Wrap/Wrap';
4 | import classes from './PasswordShowHide.module.css';
5 |
6 | class PassShowHide extends React.Component {
7 |
8 | state = {
9 | hidden: true,
10 | password: ''
11 | };
12 |
13 | handlePasswordChange = (e) => {
14 | this.setState({ password: e.target.value });
15 | this.props.senddata(this.props.valueKey,this.state.password);
16 | }
17 | toggleShow = () => {
18 | this.setState({ hidden: !this.state.hidden });
19 | }
20 |
21 | render(){
22 |
23 | return(
24 |
25 |
26 |
27 |
34 |
35 |
36 | {
37 | this.state.hidden ?
38 |
:
39 |
40 | }
41 |
42 |
43 |
44 |
45 | )
46 | }
47 | }
48 | export default PassShowHide;
49 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Blog App
2 | [](https://gitter.im/ExpressHermesOSC/Blog-App?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
3 |
4 | A blog app built with django api for backend and reactjs for frontend.
5 |
6 | ## Requirements
7 | ### For backend
8 | - Python
9 | - Django
10 |
11 | ### For backend
12 | - Yarn
13 | - React
14 |
15 |
16 | ## Frontend
17 | Open frontend folder for reactjs and project frontend.
18 |
19 | ## Backend API
20 | Open backend folder README for installation and instructions for Django API.
21 |
22 | ## Contribution
23 | - Fork and clone the repo.
24 | - To avoid merge conflicts, make sure to set upstream in your git.
25 | ```
26 | git remote add upstream https://github.com/ExpressHermes/Blog-API.git
27 | ```
28 | - Whenever you want to pull changes from main repo, run:
29 | ```
30 | git pull upstream main
31 | ```
32 | - Create your feature branch
33 | ```
34 | git checkout -b
35 | ```
36 | - Commit your changes
37 | ```
38 | git commit -am "Meaningful commit message"
39 | ```
40 | - Push to the branch
41 | ```
42 | git push origin
43 | ```
44 |
45 | - If you see any bug or you have a feature suggestion, create an issue.
46 | - Start working on an issue only after it has been approved by the maintainer.
47 | - Wait till the end of the day to get the reply on an issue or review of a PR.
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/frontend/blog-app/src/Components/UI/Toolbar/Navigation/NavigationItems/NavigationItem/NavigationItem.module.css:
--------------------------------------------------------------------------------
1 | .link{
2 | display: inline-block;
3 | height: 40px;
4 | box-sizing: border-box;
5 | padding: 2.5px 20px;
6 | margin: 12px 5px;
7 | text-decoration: none;
8 | /* border-radius: 4px; */
9 | transition: all ease 0.2s;
10 | /* transition: background-color ease 0.2s; */
11 | transition: padding ease 0.2s;
12 | line-height: 33px;
13 | /* background-color: rgb(6, 29, 44); */
14 | /* border: 1px solid rgb(6, 29, 44); */
15 | color: #eee;
16 | }
17 |
18 | .activeLink{
19 | /* background-color: #ffffff;
20 | color: rgb(6, 29, 44); */
21 | border-bottom: 2px solid #eee;
22 | }
23 |
24 | @media screen and (min-width: 900px){
25 | .link:hover{
26 | border-bottom: 2px solid #eee;
27 | }
28 | }
29 |
30 | @media screen and (max-width: 900px) {
31 | .link {
32 | display: block;
33 | height: 50px;
34 | width: 100%;
35 | line-height: 48px;
36 | margin: 20px 0;
37 | padding: 0 10px;
38 | color:rgb(138, 23, 80);
39 | text-decoration: none;
40 | border-radius: 0px;
41 | font-size: 18px;
42 | background-color: white;
43 | border: none;
44 | border-bottom: 1px solid rgb(138, 23, 80);
45 | box-sizing: border-box;
46 | font-weight: bold;
47 | transition: 0.1s all;
48 | }
49 |
50 | .activeLink,
51 | .link:hover,
52 | .link:active {
53 | color: rgb(138, 23, 80);
54 | background-color: white;
55 | border-bottom: 4px solid rgb(138, 23, 80);
56 | }
57 |
58 | .auth{
59 | border: 2px solid black;
60 | width: 70%;
61 | border-radius: 10px;
62 | margin: 0 auto;
63 | text-align: center;
64 | margin-top: 50px;
65 | }
66 | }
--------------------------------------------------------------------------------
/backend/blog/urls.py:
--------------------------------------------------------------------------------
1 | from django.conf import settings
2 | from django.conf.urls.static import static
3 | from django.contrib import admin
4 | from django.urls import path, include, re_path
5 | # documentation
6 | from rest_framework import permissions
7 | from drf_yasg.views import get_schema_view
8 | from drf_yasg import openapi
9 |
10 |
11 | schema_view = get_schema_view(
12 | openapi.Info(
13 | title="Blog API",
14 | default_version="v1",
15 | description="Blog API using Django rest framework",
16 | terms_of_service="https://www.google.com/policies/terms/",
17 | contact=openapi.Contact(email="contact@snippets.local"),
18 | license=openapi.License(name="BSD License"),
19 | ),
20 | public=True,
21 | permission_classes=[permissions.AllowAny],
22 | )
23 |
24 |
25 | urlpatterns = [
26 | path("admin/", admin.site.urls),
27 |
28 | # documentation urls
29 | re_path(
30 | r"^swagger(?P\.json|\.yaml)$",
31 | schema_view.without_ui(cache_timeout=0),
32 | name="schema-json",
33 | ),
34 | re_path(
35 | r"^swagger/$",
36 | schema_view.with_ui("swagger", cache_timeout=0),
37 | name="schema-swagger-ui",
38 | ),
39 | re_path(
40 | r"^redoc/$", schema_view.with_ui("redoc", cache_timeout=0), name="schema-redoc"
41 | ),
42 |
43 | # browser login
44 | path("api-auth/", include("rest_framework.urls")),
45 |
46 | # api authentication and token generation
47 | path("user/", include("accounts.urls", namespace="accounts")),
48 |
49 | # api
50 | path("posts/", include("posts.urls", namespace="posts_api")),
51 | ]
52 |
53 | # media urls
54 | urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
55 |
--------------------------------------------------------------------------------
/frontend/blog-app/src/Containers/Authentication/Signin/Signin.module.css:
--------------------------------------------------------------------------------
1 | .brandMsg{
2 | padding: 60px 0 0;
3 | text-align: center;
4 | font-size: 30px;
5 | }
6 |
7 | .brandLogo{
8 | width: 100px;
9 | height: 100px;
10 | border: 2px solid black;
11 | display: block;
12 | margin: 0 auto 20px;
13 | font-size: 20px;
14 | }
15 |
16 | .external{
17 | display: block;
18 | margin: 20px auto;
19 | padding: 30px;
20 | background-color: #edeff1;
21 | text-align: center;
22 | width: 90%;
23 | max-width: 460px;
24 | box-sizing: border-box;
25 | }
26 |
27 | .wel_quote{
28 | color: rgb(87, 87, 87);
29 | }
30 |
31 | .input{
32 | background-color: #aaaaaa00;
33 | width: 90%;
34 | padding: 5px;
35 | margin: 15px auto;
36 | height: 32px;
37 | font-size: 18px;
38 | border: none;
39 | border-bottom: 2px solid grey;
40 | display: block;
41 | text-align: left;
42 | }
43 |
44 | .input:focus{
45 | outline: none;
46 | background-color: #e9f1ff;
47 | border-color: rgb(44, 119, 216);
48 | }
49 |
50 | button{
51 | background-color: #eee;
52 | border: none;
53 | height: 30px;
54 | }
55 |
56 | button.signin{
57 | background-color: #2ea44f;
58 | color: white;
59 | display: block;
60 | width: 90%;
61 | max-width: 400px;
62 | padding: 5px;
63 | margin: 10px auto;
64 | border-radius: 15px;
65 | height: 40px;
66 | margin-top: 30px;
67 | font-size: 18px;
68 | }
69 |
70 | button.signin:focus, button.signin:hover{
71 | outline: none;
72 | background-color: #4baa66;
73 | }
74 |
75 | .newToAppMsg, .forgotMsg{
76 | font-size: 18px;
77 | color: rgb(105, 105, 105);
78 | margin-top: 20px;
79 | }
80 |
81 | .newToAppMsg a,.forgotMsg a{
82 | color: rgb(31, 116, 185);
83 | text-decoration: none;
84 | font-weight: bold;
85 | margin: 0 10px;
86 | display: inline-block;
87 | }
88 | .newToAppMsg a:hover,.forgotMsg a:hover{
89 | color: rgb(69, 136, 190);
90 | }
--------------------------------------------------------------------------------
/frontend/blog-app/src/Containers/Home/Home.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router-dom';
3 | import Wrap from '../../hoc/Wrap/Wrap';
4 | import classes from './Home.module.css';
5 |
6 | class Home extends React.Component{
7 | render = () => (
8 |
9 |
10 |
11 | The following text is a sample text and also informative for the further development of this app.
12 |
13 | This is the Home Page.
14 |
15 | The nav bar is made responsive and for that many components are added.
16 |
17 | Coming to the structure of the components:
18 | Till now Module.css files are used and are locally scoped, hence changin any styles in any files will not interfere with the things that are right now present.
19 | Also each component has its folder with the component name as the folder name, inside it, there will be two files module.css file and the js file.
20 | There are two big folders, Components and Containers. Components are such which does depend on the nearby components, such as the things that matter the UI local state and the functional components.
21 |
22 | Components are such, they will manage the state of the whole app.
23 |
24 | hoc are helper Higher Order Components.
25 | Wrap is one such component made for ease of returning multiple things in a component. It just wraps the chunks without help of a div.
26 |
27 |
28 | New page added "Profile page" check here to go Here
29 |
30 | --- Ravi Sri Ram Chowdary (@ravisrc)
31 |
32 |
33 |
34 |
35 | )
36 | }
37 |
38 | export default Home;
--------------------------------------------------------------------------------
/backend/accounts/views.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth import get_user_model
2 | from django.shortcuts import render
3 | from rest_framework.permissions import (
4 | AllowAny,
5 | IsAuthenticated,
6 | IsAuthenticatedOrReadOnly,
7 | )
8 | from rest_framework.response import Response
9 | from rest_framework.status import HTTP_200_OK, HTTP_400_BAD_REQUEST
10 | from rest_framework.views import APIView
11 | from rest_framework.generics import (
12 | CreateAPIView,
13 | DestroyAPIView,
14 | ListAPIView,
15 | ListCreateAPIView,
16 | RetrieveUpdateDestroyAPIView,
17 | )
18 | from posts.permissions import IsOwnerOrReadOnly, IsOwner
19 | from .serializers import UserSerializer
20 |
21 | # Create your views here.
22 |
23 | User = get_user_model()
24 |
25 |
26 | class UserCreateAPIView(CreateAPIView):
27 | """
28 | post:
29 | Create new user instance. Returns username, email of the created user.
30 |
31 | parameters: [username, email, password]
32 | """
33 |
34 | permission_classes = [AllowAny]
35 | serializer_class = UserSerializer
36 |
37 | class UserListAPIView(ListAPIView):
38 | """
39 | get:
40 | Returns list of all exisiting users
41 | """
42 |
43 | queryset = User.objects.all()
44 | permission_classes = [IsAuthenticated]
45 | serializer_class = UserSerializer
46 |
47 |
48 | class UserDetailAPIView(RetrieveUpdateDestroyAPIView):
49 | """
50 | get:
51 | Returns the detail of a user instance
52 |
53 | parameters: [id]
54 |
55 | put:
56 | Update the detail of a user instance
57 |
58 | parameters: [id, username, email, password]
59 |
60 | delete:
61 | Delete a user instance
62 |
63 | parameters: [id]
64 | """
65 |
66 | permission_classes = [IsAuthenticatedOrReadOnly, IsOwner]
67 | queryset = User.objects.all()
68 | serializer_class = UserSerializer
69 | lookup_field = 'id'
--------------------------------------------------------------------------------
/backend/posts/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.1.3 on 2020-12-19 14:26
2 |
3 | from django.conf import settings
4 | from django.db import migrations, models
5 | import django.db.models.deletion
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | initial = True
11 |
12 | dependencies = [
13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL),
14 | ]
15 |
16 | operations = [
17 | migrations.CreateModel(
18 | name='Post',
19 | fields=[
20 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
21 | ('slug', models.SlugField()),
22 | ('title', models.CharField(max_length=100)),
23 | ('body', models.TextField()),
24 | ('description', models.CharField(max_length=200)),
25 | ('created_at', models.DateTimeField(auto_now_add=True)),
26 | ('updated_at', models.DateTimeField(auto_now=True)),
27 | ('image', models.ImageField(blank=True, null=True, upload_to='media')),
28 | ('author', models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
29 | ],
30 | options={
31 | 'ordering': ['-created_at', '-updated_at'],
32 | },
33 | ),
34 | migrations.CreateModel(
35 | name='Comment',
36 | fields=[
37 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
38 | ('body', models.TextField()),
39 | ('created_at', models.DateTimeField(auto_now_add=True)),
40 | ('updated_at', models.DateTimeField(auto_now=True)),
41 | ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
42 | ('parent', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='posts.post')),
43 | ],
44 | options={
45 | 'ordering': ['-created_at', '-updated_at'],
46 | },
47 | ),
48 | ]
49 |
--------------------------------------------------------------------------------
/frontend/blog-app/src/Components/UI/Toolbar/Toolbar.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Navigation from './Navigation/Navigation';
3 | import Wrap from '../../../hoc/Wrap/Wrap';
4 | import HamBurger from './HamBurger/HamBurger';
5 | import Backdrop from '../Backdrop/Backdrop';
6 | import SideDrawer from '../SideDrawer/SideDrawer';
7 |
8 | import classes from './Toolbar.module.css';
9 |
10 | class ToolBar extends React.Component{
11 | state = {
12 | isOpened: false
13 | };
14 | render(){
15 |
16 | return(
17 |
18 |
19 |
20 |
21 |
22 |
23 |
{
26 | this.setState((prevState) => {
27 | return {
28 | isOpened: !prevState.isOpened,
29 | };
30 | });
31 | }}
32 | />
33 | {
36 | this.setState((prevState) => {
37 | return {
38 | isOpened: false,
39 | };
40 | });
41 | }}
42 | />
43 | {
46 | this.setState((prevState) => {
47 | return {
48 | isOpened: false,
49 | };
50 | });
51 | }}
52 | />
53 |
54 |
55 | )
56 | }
57 | }
58 |
59 | export default ToolBar;
--------------------------------------------------------------------------------
/frontend/blog-app/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
19 |
20 |
21 |
22 |
31 | Blog App - Welcome to a new world of blogs
32 |
33 |
34 | You need to enable JavaScript to run this app.
35 |
36 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/backend/posts/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 | from django.contrib.auth import get_user_model
3 | from django.db.models.signals import pre_save
4 | from django.utils.text import slugify
5 | from django.shortcuts import reverse
6 |
7 | User = get_user_model()
8 |
9 | # Create your models here.
10 | class Post(models.Model):
11 | slug = models.SlugField()
12 | title = models.CharField(max_length=100)
13 | body = models.TextField()
14 | author = models.ForeignKey(User, on_delete=models.CASCADE, default=1)
15 | description = models.CharField(max_length=200)
16 | created_at = models.DateTimeField(auto_now=False, auto_now_add=True)
17 | updated_at = models.DateTimeField(auto_now=True, auto_now_add=False)
18 | image = models.ImageField(upload_to='media', blank=True, null=True)
19 |
20 | def __str__(self):
21 | return self.title
22 |
23 | class Meta:
24 | ordering = ["-created_at", "-updated_at"]
25 |
26 | def get_api_url(self):
27 | try:
28 | return reverse("posts_api:post_detail", kwargs={"slug": self.slug})
29 | except:
30 | None
31 |
32 | @property
33 | def comments(self):
34 | instance = self
35 | qs = Comment.objects.filter(parent=instance)
36 | return qs
37 |
38 |
39 | def create_slug(instance, new_slug=None):
40 | slug = slugify(instance.title)
41 | if new_slug is not None:
42 | slug = new_slug
43 | qs = Post.objects.filter(slug=slug).order_by("-id")
44 | exists = qs.exists()
45 | if exists:
46 | new_slug = "%s-%s" % (slug, qs.first().id)
47 | return create_slug(instance, new_slug=new_slug)
48 | return slug
49 |
50 |
51 | def pre_save_post_receiver(sender, instance, *args, **kwargs):
52 | if not instance.slug:
53 | instance.slug = create_slug(instance)
54 |
55 |
56 | pre_save.connect(pre_save_post_receiver, sender=Post)
57 |
58 |
59 | class Comment(models.Model):
60 | parent = models.ForeignKey(Post, on_delete=models.CASCADE)
61 | author = models.ForeignKey(User, on_delete=models.CASCADE)
62 | body = models.TextField()
63 | created_at = models.DateTimeField(auto_now=False, auto_now_add=True)
64 | updated_at = models.DateTimeField(auto_now=True, auto_now_add=False)
65 |
66 | class Meta:
67 | ordering = ["-created_at", "-updated_at"]
68 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Editor settings
2 | .vscode
3 |
4 | # Byte-compiled / optimized / DLL files
5 | __pycache__/
6 | *.py[cod]
7 | *$py.class
8 |
9 | # C extensions
10 | *.so
11 |
12 | # Distribution / packaging
13 | .Python
14 | build/
15 | develop-eggs/
16 | dist/
17 | downloads/
18 | eggs/
19 | .eggs/
20 | lib/
21 | lib64/
22 | parts/
23 | sdist/
24 | var/
25 | wheels/
26 | pip-wheel-metadata/
27 | share/python-wheels/
28 | *.egg-info/
29 | .installed.cfg
30 | *.egg
31 | MANIFEST
32 |
33 | # PyInstaller
34 | # Usually these files are written by a python script from a template
35 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
36 | *.manifest
37 | *.spec
38 |
39 | # Installer logs
40 | pip-log.txt
41 | pip-delete-this-directory.txt
42 |
43 | # Unit test / coverage reports
44 | htmlcov/
45 | .tox/
46 | .nox/
47 | .coverage
48 | .coverage.*
49 | .cache
50 | nosetests.xml
51 | coverage.xml
52 | *.cover
53 | *.py,cover
54 | .hypothesis/
55 | .pytest_cache/
56 |
57 | # Translations
58 | *.mo
59 | *.pot
60 |
61 | # Django stuff:
62 | *.log
63 | local_settings.py
64 | db.sqlite3
65 | db.sqlite3-journal
66 |
67 | # Flask stuff:
68 | instance/
69 | .webassets-cache
70 |
71 | # Scrapy stuff:
72 | .scrapy
73 |
74 | # Sphinx documentation
75 | docs/_build/
76 |
77 | # PyBuilder
78 | target/
79 |
80 | # Jupyter Notebook
81 | .ipynb_checkpoints
82 |
83 | # IPython
84 | profile_default/
85 | ipython_config.py
86 |
87 | # pyenv
88 | .python-version
89 |
90 | # pipenv
91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
94 | # install all needed dependencies.
95 | #Pipfile.lock
96 |
97 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
98 | __pypackages__/
99 |
100 | # Celery stuff
101 | celerybeat-schedule
102 | celerybeat.pid
103 |
104 | # SageMath parsed files
105 | *.sage.py
106 |
107 | # Environments
108 | .env
109 | .venv
110 | env/
111 | venv/
112 | ENV/
113 | env.bak/
114 | venv.bak/
115 |
116 | # Spyder project settings
117 | .spyderproject
118 | .spyproject
119 |
120 | # Rope project settings
121 | .ropeproject
122 |
123 | # mkdocs documentation
124 | /site
125 |
126 | # mypy
127 | .mypy_cache/
128 | .dmypy.json
129 | dmypy.json
130 |
131 | # Pyre type checker
132 | .pyre/
--------------------------------------------------------------------------------
/frontend/blog-app/src/Containers/Authentication/ResetPass/ResetPass.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import Wrap from '../../../hoc/Wrap/Wrap';
4 | import classes from '../Auth.module.css';
5 | import PasswordShowHide from '../../../Components/UI/PasswordShowHide/PasswordShowHide';
6 |
7 | import {Link} from 'react-router-dom';
8 |
9 | class ResetPass extends React.Component{
10 |
11 | state = {
12 | email : '',
13 | password : '',
14 | rePassword : ''
15 | }
16 |
17 | componentDidMount(){
18 | document.title = "Sign In - Blog App"
19 | }
20 |
21 | getData = (key,val) => {
22 | this.setState((prevState) => ({
23 | ...prevState,
24 | [key] : val
25 | })
26 | )
27 | }
28 |
29 | render(){
30 | // This email will be coming from either a unique key/id/token the link params of the confirmation
31 | // At present it is being passed directly
32 | let email = "expresshermes@expherm.org";
33 | let name,domain,emailName,maskedMail;
34 | if (email.length > 0){
35 | [name,domain] = email.split('@');
36 | emailName = new Array(name.length - 1).join('*');
37 | emailName = name[0] + emailName + name[name.length - 1];
38 | maskedMail = emailName + '@' + domain;
39 | }
40 |
41 | return(
42 |
43 |
44 |
45 |
46 |
47 | Reset Password
48 |
49 |
50 |
51 |
52 | Use the following wizard for resetting password of
53 |
54 | {maskedMail}
55 |
56 |
57 |
58 |
59 |
64 |
69 |
70 |
Submit
71 |
Cancel
72 |
73 |
74 |
75 |
76 | This is just a development stage message:
77 | l be coming from either a unique key/id/token the link params of the confirmation mail
78 | At present it i passed directly
79 |
80 |
81 | )
82 | }
83 | }
84 |
85 | export default ResetPass;
--------------------------------------------------------------------------------
/frontend/blog-app/src/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/blog-app/src/Containers/Authentication/Auth.module.css:
--------------------------------------------------------------------------------
1 | .brandMsg{
2 | padding: 60px 0 0;
3 | text-align: center;
4 | font-size: 30px;
5 | }
6 |
7 | .brandLogo{
8 | width: 100px;
9 | height: 100px;
10 | display: block;
11 | margin: 0 auto 20px;
12 | font-size: 20px;
13 | }
14 |
15 | .brandLogo img{
16 | width: 100%;
17 | height: 100%;
18 | }
19 |
20 | .external{
21 | display: block;
22 | margin: 20px auto;
23 | padding: 30px;
24 | background-color: #edeff1;
25 | text-align: center;
26 | width: 90%;
27 | max-width: 460px;
28 | box-sizing: border-box;
29 | color: rgb(87, 87, 87);
30 | }
31 |
32 | .wel_quote{
33 | color: rgb(87, 87, 87);
34 | }
35 |
36 | .input{
37 | background-color: #aaaaaa00;
38 | width: 90%;
39 | padding: 5px;
40 | margin: 15px auto;
41 | height: 32px;
42 | font-size: 18px;
43 | border: none;
44 | border-bottom: 2px solid grey;
45 | display: block;
46 | text-align: left;
47 | color: unset;
48 | }
49 |
50 | .input:focus{
51 | outline: none;
52 | background-color: #e9f1ff;
53 | border-color: rgb(44, 119, 216);
54 | }
55 |
56 | .InValid{
57 | background-color: #ffe9e9;
58 | border-color: rgb(216, 44, 44);
59 | }
60 |
61 | button{
62 | background-color: #eee;
63 | border: none;
64 | height: 30px;
65 | }
66 |
67 | button.signin{
68 | background-color: #2ea44f;
69 | color: white;
70 | display: block;
71 | width: 90%;
72 | max-width: 400px;
73 | padding: 5px;
74 | margin: 10px auto;
75 | border-radius: 15px;
76 | height: 40px;
77 | margin-top: 30px;
78 | font-size: 18px;
79 | }
80 |
81 | button.signin:focus, button.signin:hover{
82 | outline: none;
83 | background-color: #4baa66;
84 | }
85 |
86 | button.cancel{
87 | background-color: #bbbbbb;
88 | color: rgb(39, 39, 39);
89 | display: block;
90 | width: 90%;
91 | max-width: 400px;
92 | padding: 5px;
93 | margin: 10px auto;
94 | border-radius: 15px;
95 | height: 40px;
96 | font-size: 18px;
97 | }
98 |
99 | button.cancel:focus, button.cancel:hover{
100 | outline: none;
101 | background-color: #afafaf;
102 | }
103 |
104 | .newToAppMsg, .forgotMsg{
105 | font-size: 18px;
106 | color: rgb(105, 105, 105);
107 | margin-top: 20px;
108 | }
109 |
110 | .newToAppMsg a,.forgotMsg a{
111 | color: rgb(31, 116, 185);
112 | text-decoration: none;
113 | font-weight: bold;
114 | margin: 0 10px;
115 | display: inline-block;
116 | }
117 | .newToAppMsg a:hover,.forgotMsg a:hover{
118 | color: rgb(69, 136, 190);
119 | }
120 |
121 | .bold{
122 | display: inline-block;
123 | margin-left: 5px;
124 | }
125 |
126 | label{
127 | color: rgb(87, 87, 87);
128 | font-size: 18px;
129 | margin: 0 5px;
130 | }
131 |
132 | .accept{
133 | display: block;
134 | }
--------------------------------------------------------------------------------
/frontend/blog-app/src/Containers/Profile/Profile.module.css:
--------------------------------------------------------------------------------
1 | .profCard{
2 | padding: 20px 10px 30px;
3 | box-sizing: border-box;
4 | text-align: center;
5 | background-color: rgb(243, 242, 242);
6 | }
7 |
8 | .avatarHolder{
9 | width: 120px;
10 | height: 120px;
11 | background-color: #fff;
12 | /* border: 1px solid grey; */
13 | border-radius: 1000px;
14 | overflow: hidden;
15 | margin: 60px 0 20px;
16 | display: inline-block;
17 | }
18 |
19 | .avatar{
20 | height: 100%;
21 | width: 100%;
22 | }
23 |
24 | .username{
25 | font-weight: bold;
26 | }
27 |
28 | .fullname{
29 | font-size: 150%;
30 | padding: 10px 0;
31 | }
32 |
33 | .email, .blogs, .followers{
34 | padding: 5px 0;
35 | font-size: 110%;
36 | }
37 |
38 | .secHead{
39 | color: rgb(6, 29, 44);
40 | font-weight: bolder;
41 | margin-bottom: 10px;
42 | }
43 |
44 | .clearBoth{
45 | clear: both;
46 | }
47 |
48 | .secCover{
49 | margin: 10px;
50 | padding: 10px;
51 | background-color: rgb(243,242,242);
52 | }
53 |
54 | .info{
55 | display: block;
56 | }
57 |
58 | .activity{
59 | padding: 5px;
60 | }
61 |
62 | .comment{
63 | border-left: 3px solid grey;
64 | margin: 10px;
65 | padding: 5px 10px;
66 | background-color: #eee;
67 | color: #777;
68 | }
69 |
70 | .acName, .blogName, .blogTitle{
71 | font-weight: bold;
72 | font-style: italic;
73 | color: rgb(28, 24, 104);
74 | display: inline-block;
75 | }
76 |
77 | .blogName{
78 | color: indianred;
79 | }
80 |
81 | .time{
82 | color: rgb(61, 61, 61);
83 | display: block;
84 | font-size: 80%;
85 | }
86 |
87 | .blogTitle{
88 | display: block;
89 | color: indianred;
90 | }
91 |
92 | .blog{
93 | margin: 20px 10px;
94 | border-left: 3px solid grey;
95 | padding-left: 10px;
96 | }
97 |
98 | .commentInfo, .likeInfo{
99 | display: inline-block;
100 | margin: 5px;
101 | color: #666;
102 | font-size: 87%;
103 | }
104 |
105 | @media screen and (min-width: 940px){
106 | .profCard{
107 | width: 370px;
108 | margin-left: 10px;
109 | float: left;
110 | }
111 |
112 | .info{
113 | margin-left: 380px;
114 | }
115 | }
116 |
117 | @media screen and (max-width: 940px) and (min-width: 800px){
118 | .profCard{
119 | width: 40%;
120 | margin-left: 10px;
121 | float: left;
122 | }
123 |
124 | .info{
125 | margin-left: calc(40vw);
126 | }
127 | }
128 |
129 | @media screen and (max-width: 800px){
130 | .profCard{
131 | margin: 10px;
132 | float: none;
133 | }
134 |
135 | .info{
136 | margin-left: 0;
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/frontend/blog-app/src/Containers/Authentication/ForgotPass/ForgotPass.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router-dom';
3 |
4 | import Wrap from '../../../hoc/Wrap/Wrap';
5 | import classes from '../Auth.module.css';
6 |
7 | // import {Link} from 'react-router-dom';
8 |
9 | class ForgotPass extends React.Component{
10 |
11 | state = {
12 | sent : false,
13 | email : ''
14 | }
15 |
16 | render(){
17 |
18 | // Let an email address entered and will be passed to the Backend server and if succesull the confirmation will be displayed
19 | let name,domain,emailName,maskedMail;
20 | if (this.state.email.length > 0 && this.state.sent){
21 | [name,domain] = this.state.email.split('@');
22 | emailName = new Array(name.length - 1).join('*');
23 | emailName = name[0] + emailName + name[name.length - 1];
24 | maskedMail = emailName + '@' + domain;
25 | }
26 |
27 | return(
28 | !this.state.sent ?
29 |
30 |
31 |
32 |
33 |
34 | Reset Password
35 |
36 |
37 |
38 |
39 | Use the following wizard to get the Verification link
40 |
41 |
42 |
{
47 | this.setState((prevState) => ({
48 | ...prevState,
49 | email: e.target.value
50 | }))
51 | }}
52 | />
53 |
{
57 | this.setState((prevState) => ({
58 | ...prevState,
59 | sent: true
60 | }))
61 | }}
62 | >
63 | Send Link
64 |
65 |
66 |
67 |
68 | This is just a development stage message: enter a valid email and see magic.
69 |
70 |
71 | :
72 |
73 |
74 |
75 |
76 |
77 | Reset Password
78 |
79 |
80 |
81 |
82 | Succesfully sent the Verification link to {maskedMail}
83 |
84 |
85 |
86 |
87 | This is just a development stage message: After this user may get a lnk which redirects him to following page:
88 | ./resetPass
89 |
90 |
91 | )
92 | }
93 | }
94 |
95 | export default ForgotPass;
--------------------------------------------------------------------------------
/frontend/blog-app/src/Containers/Authentication/Signin/Signin.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import Wrap from '../../../hoc/Wrap/Wrap';
4 | import classes from '../Auth.module.css';
5 |
6 | import PasswordShowHide from '../../../Components/UI/PasswordShowHide/PasswordShowHide';
7 |
8 | import {Link} from 'react-router-dom';
9 |
10 | class Signin extends React.Component{
11 |
12 | state = {
13 | username : '',
14 | password : ''
15 | }
16 |
17 | error = null;
18 | loading = false;
19 |
20 | handleFormChange = (e) => {
21 | var key = e.target.name;
22 | this.setState({ [key] : e.target.value});
23 | }
24 |
25 | handleSubmit = (e) => {
26 | this.loading = true;
27 | var data = {
28 | "username" : this.state.username,
29 | "password" : this.state.password
30 | }
31 | fetch("http://127.0.0.1:8000/api-auth/login/",{
32 | method : "POST",
33 | headers : {
34 | "Content-Type": "application/json",
35 | },
36 | body : JSON.stringify(data),
37 | })
38 | .then((res) => res.json() )
39 | .then((json) => {
40 | console.log(json);
41 | this.loading = false;
42 | // Redirect to dashboard
43 | })
44 | .catch((err) => {
45 | console.log(err);
46 | this.error = err;
47 | this.loading = false;
48 | })
49 | }
50 |
51 | componentDidMount(){
52 | document.title = "Sign In - Blog App"
53 | }
54 |
55 | getData = (key,val) => {
56 | this.setState((prevState) => ({
57 | ...prevState,
58 | [key] : val
59 | })
60 | )
61 | }
62 |
63 | render(){
64 | console.log(this.state);
65 | return(
66 |
67 |
68 |
69 |
70 |
71 | Sign In to Blog App
72 |
73 |
74 |
75 |
76 | Signin to start writing and enter a new world of Blogs
77 |
78 |
79 |
80 |
81 |
86 |
87 |
88 | Forgot Password?
89 |
90 |
Sign In
91 |
92 |
93 |
94 | New to Blog App?
95 | Create Account
96 |
97 |
98 |
99 | This is just a development stage message: check all the functionalities like resetPass, forgotPass, verificationMail and all
100 |
101 |
102 | )
103 | }
104 | }
105 |
106 | export default Signin;
--------------------------------------------------------------------------------
/backend/posts/serializers.py:
--------------------------------------------------------------------------------
1 | import os
2 | from django.conf import settings
3 | from django.contrib.auth import get_user_model
4 | from rest_framework import serializers
5 | from .models import Post, Comment
6 |
7 | User = get_user_model()
8 |
9 | class PostCreateUpdateSerializer(serializers.ModelSerializer):
10 | class Meta:
11 | model = Post
12 | fields = [
13 | "title",
14 | "description",
15 | "body",
16 | "image"
17 | ]
18 |
19 | def validate_title(self, value):
20 | if len(value) > 100:
21 | return serializers.ValidationError("Max title length is 100 characters")
22 | return value
23 |
24 | def validate_description(self, value):
25 | if len(value) > 200:
26 | return serializers.ValidationError(
27 | "Max description length is 200 characters"
28 | )
29 | return value
30 |
31 | def clean_image(self, value):
32 | initial_path = value.path
33 | new_path = settings.MEDIA_ROOT + value.name
34 | os.rename(initial_path, new_path)
35 | return value
36 |
37 |
38 | class PostListSerializer(serializers.ModelSerializer):
39 | url = serializers.SerializerMethodField()
40 | comments = serializers.SerializerMethodField(read_only=True)
41 |
42 | class Meta:
43 | model = Post
44 | fields = [
45 | "id",
46 | "url",
47 | "title",
48 | "author",
49 | "image",
50 | "description",
51 | "comments",
52 | ]
53 |
54 | def get_comments(self, obj):
55 | qs = Comment.objects.filter(parent=obj).count()
56 | return qs
57 |
58 | def get_url(self, obj):
59 | return obj.get_api_url()
60 |
61 |
62 | class PostDetailSerializer(serializers.ModelSerializer):
63 | slug = serializers.SerializerMethodField(read_only=True)
64 | author = serializers.PrimaryKeyRelatedField(read_only=True)
65 | comments = serializers.SerializerMethodField(read_only=True)
66 |
67 | class Meta:
68 | model = Post
69 | fields = [
70 | "id",
71 | "slug",
72 | "title",
73 | "description",
74 | "body",
75 | "author",
76 | "image",
77 | "created_at",
78 | "updated_at",
79 | "comments",
80 | ]
81 |
82 | def get_slug(self, obj):
83 | return obj.slug
84 |
85 | def get_comments(self, obj):
86 | qs = Comment.objects.filter(parent=obj)
87 | try:
88 | serializer = CommentSerializer(qs, many=True)
89 | except Exception as e:
90 | print(e)
91 | return serializer.data
92 |
93 |
94 | class CommentSerializer(serializers.ModelSerializer):
95 | class Meta:
96 | model = Comment
97 | fields = [
98 | "id",
99 | "parent",
100 | "author",
101 | "body",
102 | "created_at",
103 | "updated_at",
104 | ]
105 |
106 |
107 | class CommentCreateUpdateSerializer(serializers.ModelSerializer):
108 | class Meta:
109 | model = Comment
110 | fields = [
111 | "body",
112 | ]
113 |
--------------------------------------------------------------------------------
/frontend/blog-app/src/Components/UI/Footer/Footer.module.css:
--------------------------------------------------------------------------------
1 | /*================FOOTER===============================*/
2 |
3 | .footer{
4 | background-color: rgb(66, 5, 36);
5 | }
6 |
7 | .fpt a{
8 | color: inherit;
9 | text-decoration: none;
10 | }
11 |
12 | .fpt a:hover{
13 | color: inherit;
14 | text-decoration: underline;
15 | }
16 |
17 | .clearboth{
18 | clear: both;
19 | }
20 |
21 | @media screen and (min-width: 700px){
22 | .footer{
23 | height: 300px;
24 | position: relative;
25 | }
26 |
27 | .fpt {
28 | width: 26.66%;
29 | height: 100%;
30 | position: absolute;
31 | top: 0;
32 | text-align: center;
33 | }
34 |
35 | .fpt2{
36 | left:10%;
37 | }
38 | .fpt3{
39 | left: 36%;
40 | }
41 | .fpt4{
42 | left:62%;
43 | }
44 |
45 | .fpt h2{
46 | font-size: 20px;
47 | display: inline-block;
48 | margin: 30px auto 15px;
49 | color: #eee;
50 | }
51 |
52 | .fpt ul{
53 | list-style-type: none;
54 | font-size: 18px;
55 | padding: 0;
56 | color: #bbb;
57 | }
58 |
59 | .fpt ul li{
60 | margin: 15px auto;
61 | }
62 | }
63 |
64 | @media screen and (min-width: 800px) and (max-width: 1336px){
65 | .fpt {
66 | width: 25%;
67 | }
68 | .fpt2{
69 | left:12.5%;
70 | }
71 | .fpt3{
72 | left: 37.5%;
73 | }
74 | .fpt4{
75 | left: 62.5%;
76 | }
77 | .fpt h2{
78 | font-size: 19px;
79 | }
80 | .fpt ul{
81 | font-size: 17px;
82 | }
83 | }
84 |
85 | @media screen and (min-width: 700px) and (max-width: 800px){
86 | .fpt {
87 | width: 33%;
88 | }
89 | .fpt2{
90 | left:0.5%;
91 | }
92 | .fpt3{
93 | left: 33.5%;
94 | }
95 | .fpt4{
96 | left: 66.5%;
97 | }
98 | .fpt h2{
99 | font-size: 19px;
100 | }
101 | .fpt ul{
102 | font-size: 17px;
103 | }
104 | }
105 |
106 | @media screen and (max-width: 700px){
107 | .footer{
108 | padding-top: 10px;
109 | padding-bottom: 40px;
110 | position: unset;
111 | }
112 |
113 | .fpt {
114 | position: unset;
115 | top: 0;
116 | text-align: center;
117 | }
118 |
119 | .fpt h2{
120 | font-size: 18px;
121 | display: inline-block;
122 | margin: 10px auto 5px;
123 | color: #eee;
124 | }
125 |
126 | .fpt ul{
127 | list-style-type: none;
128 | font-size: 16px;
129 | padding: 0;
130 | color: #bbb;
131 | }
132 |
133 | .fpt ul li{
134 | display: inline-block;
135 | margin: 5px 10px;
136 | }
137 | }
138 |
139 | /*===============SOCIAL MEDIA ICONS================================*/
140 | .logoholdersm{
141 | display: inline-block;
142 | width: 30px;
143 | height: 30px;
144 | margin-right: 10px;
145 | }
146 |
147 | .logoholdersm .logo{
148 | width: 100%;
149 | height: 100%;
150 | border-radius: 2000px;
151 | box-shadow: 3px 3px 6px black;
152 | }
153 |
154 | .logoholdersm .logo:hover{
155 | transform: translateY(1px);
156 | box-shadow: 1.5px 1.5px 3px black;
157 | }
158 |
159 | /*===============Copy Rights================================*/
160 |
161 | .copyRights{
162 | background-color: rgb(49, 5, 27);
163 | text-align: center;
164 | padding: 10px;
165 | color: #ccc;
166 | font-size: 18px;
167 | border-top: 0.1px solid #cccccc55;
168 | }
169 |
--------------------------------------------------------------------------------
/frontend/blog-app/README.md:
--------------------------------------------------------------------------------
1 | # Getting Started with Create React App
2 |
3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
4 |
5 | ## Available Scripts
6 |
7 | In the project directory, you can run:
8 |
9 | ### `yarn start`
10 |
11 | Runs the app in the development mode.\
12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
13 |
14 | The page will reload if you make edits.\
15 | You will also see any lint errors in the console.
16 |
17 | ### `yarn test`
18 |
19 | Launches the test runner in the interactive watch mode.\
20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
21 |
22 | ### `yarn build`
23 |
24 | Builds the app for production to the `build` folder.\
25 | It correctly bundles React in production mode and optimizes the build for the best performance.
26 |
27 | The build is minified and the filenames include the hashes.\
28 | Your app is ready to be deployed!
29 |
30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
31 |
32 | ### `yarn eject`
33 |
34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!**
35 |
36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
37 |
38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
39 |
40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
41 |
42 | ## Learn More
43 |
44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
45 |
46 | To learn React, check out the [React documentation](https://reactjs.org/).
47 |
48 | ### Code Splitting
49 |
50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
51 |
52 | ### Analyzing the Bundle Size
53 |
54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
55 |
56 | ### Making a Progressive Web App
57 |
58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
59 |
60 | ### Advanced Configuration
61 |
62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
63 |
64 | ### Deployment
65 |
66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
67 |
68 | ### `yarn build` fails to minify
69 |
70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
71 |
--------------------------------------------------------------------------------
/frontend/blog-app/src/Components/UI/Footer/Footer.js:
--------------------------------------------------------------------------------
1 | import Wrap from '../../../hoc/Wrap/Wrap';
2 | import classes from './Footer.module.css';
3 | import {Link} from 'react-router-dom';
4 |
5 | const footer = () => {
6 | let date = new Date();
7 | let year = date.getFullYear();
8 | return(
9 |
10 |
66 |
67 | © ExpressHermes {year}
68 |
69 |
70 | );
71 | }
72 |
73 | export default footer;
--------------------------------------------------------------------------------
/frontend/blog-app/src/Containers/Authentication/Signup/Signup.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import Wrap from '../../../hoc/Wrap/Wrap';
4 | import classes from '../Auth.module.css';
5 | import PasswordShowHide from '../../../Components/UI/PasswordShowHide/PasswordShowHide';
6 |
7 | import {Link} from 'react-router-dom';
8 |
9 | class SignUp extends React.Component{
10 |
11 | state = {
12 | fullname : '',
13 | username : '',
14 | email : '',
15 | dob : '',
16 | password : '',
17 | rePassword : ''
18 | }
19 |
20 | error = null;
21 | loading = false;
22 |
23 | componentDidMount(){
24 | document.title = "Create Account - Blog App"
25 | }
26 |
27 | getData = (key,val) => {
28 | this.setState((prevState) => ({
29 | ...prevState,
30 | [key] : val
31 | })
32 | )
33 | }
34 |
35 | handleFormChange = (e) => {
36 | var key = e.target.name;
37 | this.setState({ [key] : e.target.value});
38 | }
39 |
40 | handleSubmit = (e) => {
41 | this.error = null;
42 | this.loading = true;
43 | if(this.state.password === this.state.rePassword){
44 | var data = {
45 | "username" : this.state.username,
46 | "email" : this.state.email,
47 | "password" : this.state.password
48 | }
49 | fetch("http://127.0.0.1:8000/user/register/",{
50 | method: "POST",
51 | headers : {
52 | "Content-Type": "application/json",
53 | },
54 | body : JSON.stringify(data),
55 | })
56 | .then((res) => {
57 | console.log(res);
58 | console.log("Success");
59 | this.loading = false;
60 | })
61 | .catch((err) => {
62 | console.log(err);
63 | this.error = err;
64 | this.loading = false;
65 | })
66 |
67 | }
68 | else{
69 | this.error = true;
70 | this.loading = false;
71 | }
72 |
73 | }
74 |
75 | render(){
76 | console.log(this.state);
77 | return(
78 |
79 |
80 |
81 |
82 |
83 | Create Account in Blog App
84 |
85 |
86 |
119 |
120 |
121 | Already in Blog App?
122 | Sign In
123 |
124 |
125 |
126 | )
127 | }
128 | }
129 |
130 | export default SignUp;
--------------------------------------------------------------------------------
/backend/blog/settings.py:
--------------------------------------------------------------------------------
1 | """
2 | Django settings for blog project.
3 |
4 | Generated by 'django-admin startproject' using Django 3.1.3.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/3.1/topics/settings/
8 |
9 | For the full list of settings and their values, see
10 | https://docs.djangoproject.com/en/3.1/ref/settings/
11 | """
12 |
13 | from pathlib import Path
14 |
15 | # Build paths inside the project like this: BASE_DIR / 'subdir'.
16 | BASE_DIR = Path(__file__).resolve().parent.parent
17 | # TEMPLATES_DIR = BASE_DIR / "templates"
18 |
19 | # Quick-start development settings - unsuitable for production
20 | # See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/
21 |
22 | # SECURITY WARNING: keep the secret key used in production secret!
23 | SECRET_KEY = "@lu+7yr(h6^9)l+l$n^f@(n=+$685-ijv^-mt+i61*k&h-llf0"
24 |
25 | # SECURITY WARNING: don't run with debug turned on in production!
26 | DEBUG = True
27 |
28 | ALLOWED_HOSTS = []
29 |
30 |
31 | # Application definition
32 |
33 | INSTALLED_APPS = [
34 | "django.contrib.admin",
35 | "django.contrib.auth",
36 | "django.contrib.contenttypes",
37 | "django.contrib.sessions",
38 | "django.contrib.messages",
39 | "django.contrib.staticfiles",
40 | # rest framework
41 | "rest_framework",
42 | "rest_framework.authtoken",
43 | # documentation
44 | 'drf_yasg',
45 | # apps
46 | "accounts",
47 | "posts",
48 | ]
49 |
50 | LOGIN_REDIRECT_URL = "/"
51 | LOGOUT_REDIRECT_URL = "/"
52 |
53 | REST_FRAMEWORK = {
54 | "DEFAULT_RENDERER_CLASSES": (
55 | "rest_framework.renderers.JSONRenderer",
56 | "rest_framework.renderers.BrowsableAPIRenderer",
57 | ),
58 | "DEFAULT_AUTHENTICATION_CLASSES": (
59 | "rest_framework.authentication.SessionAuthentication",
60 | "rest_framework.authentication.BasicAuthentication",
61 | "rest_framework.authentication.TokenAuthentication",
62 | ),
63 | "DEFAULT_PERMISSION_CLASSES": (
64 | "rest_framework.permissions.IsAuthenticatedOrReadOnly",
65 | ),
66 | }
67 |
68 | # SWAGGER_SETTINGS = {
69 | # 'VALIDATOR_URL': 'http://localhost:8000',
70 | # }
71 |
72 |
73 | MIDDLEWARE = [
74 | "django.middleware.security.SecurityMiddleware",
75 | "django.contrib.sessions.middleware.SessionMiddleware",
76 | "django.middleware.common.CommonMiddleware",
77 | "django.middleware.csrf.CsrfViewMiddleware",
78 | "django.contrib.auth.middleware.AuthenticationMiddleware",
79 | "django.contrib.messages.middleware.MessageMiddleware",
80 | "django.middleware.clickjacking.XFrameOptionsMiddleware",
81 | ]
82 |
83 | ROOT_URLCONF = "blog.urls"
84 |
85 | TEMPLATES = [
86 | {
87 | "BACKEND": "django.template.backends.django.DjangoTemplates",
88 | "DIRS": [],
89 | "APP_DIRS": True,
90 | "OPTIONS": {
91 | "context_processors": [
92 | "django.template.context_processors.debug",
93 | "django.template.context_processors.request",
94 | "django.contrib.auth.context_processors.auth",
95 | "django.contrib.messages.context_processors.messages",
96 | ],
97 | },
98 | },
99 | ]
100 |
101 | WSGI_APPLICATION = "blog.wsgi.application"
102 |
103 |
104 | # Database
105 | # https://docs.djangoproject.com/en/3.1/ref/settings/#databases
106 |
107 | DATABASES = {
108 | "default": {
109 | "ENGINE": "django.db.backends.sqlite3",
110 | "NAME": BASE_DIR / "db.sqlite3",
111 | }
112 | }
113 |
114 |
115 | # Password validation
116 | # https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators
117 |
118 | AUTH_PASSWORD_VALIDATORS = [
119 | {
120 | "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
121 | },
122 | {
123 | "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
124 | },
125 | {
126 | "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
127 | },
128 | {
129 | "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
130 | },
131 | ]
132 |
133 |
134 | # Internationalization
135 | # https://docs.djangoproject.com/en/3.1/topics/i18n/
136 |
137 | LANGUAGE_CODE = "en-us"
138 |
139 | TIME_ZONE = "UTC"
140 |
141 | USE_I18N = True
142 |
143 | USE_L10N = True
144 |
145 | USE_TZ = True
146 |
147 |
148 | # Static files (CSS, JavaScript, Images)
149 | # https://docs.djangoproject.com/en/3.1/howto/static-files/
150 |
151 | STATIC_URL = "/static/"
152 |
153 | MEDIA_URL = "/media/"
154 | MEDIA_ROOT = BASE_DIR / "media"
155 |
--------------------------------------------------------------------------------
/frontend/blog-app/src/Containers/Profile/Profile.js:
--------------------------------------------------------------------------------
1 | import * as classes from './Profile.module.css'
2 | import React from 'react';
3 | import Wrap from '../../hoc/Wrap/Wrap';
4 |
5 | class Profile extends React.Component{
6 | render(){
7 | return(
8 |
9 |
10 |
11 |
12 |
13 |
14 | @username
15 |
16 |
17 | Full Name Here
18 |
19 |
20 | email@email.email
21 |
22 |
23 | {14} Blogs
24 |
25 |
26 | {26} Followers
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | Recent Activity
35 |
36 |
37 |
18:25, 01 Jan 2021
38 |
You commented on
ExpressHermes 's blog
A step towards Open source
39 |
40 | After reading your blog, I too got interest towards Open Source and would like to contribute to this project in first.
41 |
42 |
43 |
44 |
15:49, 28 Dec 2020
45 |
ExpressHermes commented on
your blog
My Tour
46 |
47 | How much time did it took to go to there from your location?
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | My Blogs
57 |
58 |
59 | 25 Dec 2020
60 | My Tour
61 | 58 Comments
62 | 12569 Likes
63 |
64 |
65 | 23 Nov 2020
66 | Pandemic Life
67 | 159 Comments
68 | 24875 Likes
69 |
70 |
71 | 14 Oct 2020
72 | Open source contribution
73 | 7856 Comments
74 | 145896 Likes
75 |
76 |
77 |
78 |
79 |
80 | )
81 | }
82 | }
83 |
84 | export default Profile;
85 |
--------------------------------------------------------------------------------
/backend/posts/views.py:
--------------------------------------------------------------------------------
1 | from django.shortcuts import render, redirect, get_object_or_404
2 | from rest_framework.permissions import (
3 | IsAuthenticated,
4 | IsAuthenticatedOrReadOnly,
5 | AllowAny,
6 | )
7 | from rest_framework.response import Response
8 | from rest_framework.views import APIView
9 | from rest_framework.generics import (
10 | CreateAPIView,
11 | DestroyAPIView,
12 | ListAPIView,
13 | UpdateAPIView,
14 | RetrieveAPIView,
15 | RetrieveUpdateAPIView,
16 | RetrieveUpdateDestroyAPIView,
17 | )
18 |
19 | from .pagination import PostLimitOffsetPagination
20 | from .models import Post, Comment
21 | from .permissions import IsOwnerOrReadOnly, IsOwner
22 | from .mixins import MultipleFieldLookupMixin
23 | from .serializers import (
24 | PostCreateUpdateSerializer,
25 | PostListSerializer,
26 | PostDetailSerializer,
27 | CommentSerializer,
28 | CommentCreateUpdateSerializer,
29 | )
30 |
31 | # Create your views here.
32 | class CreatePostAPIView(APIView):
33 | """
34 | post:
35 | Creates a new post instance. Returns created post data
36 |
37 | parameters: [title, body, description, image]
38 | """
39 |
40 | queryset = Post.objects.all()
41 | serializer_class = PostCreateUpdateSerializer
42 | permission_classes = [
43 | IsAuthenticated,
44 | ]
45 |
46 | def post(self, request, *args, **kwargs):
47 | serializer = PostCreateUpdateSerializer(data=request.data)
48 | if serializer.is_valid(raise_exception=True):
49 | serializer.save(author=request.user)
50 | return Response(serializer.data, status=200)
51 | else:
52 | return Response({"errors": serializer.errors}, status=400)
53 |
54 |
55 | class ListPostAPIView(ListAPIView):
56 | """
57 | get:
58 | Returns a list of all existing posts
59 | """
60 |
61 | queryset = Post.objects.all()
62 | serializer_class = PostListSerializer
63 | permission_classes = [IsAuthenticatedOrReadOnly]
64 | pagination_class = PostLimitOffsetPagination
65 |
66 |
67 | class DetailPostAPIView(RetrieveUpdateDestroyAPIView):
68 | """
69 | get:
70 | Returns the details of a post instance. Searches post using slug field.
71 |
72 | put:
73 | Updates an existing post. Returns updated post data
74 |
75 | parameters: [slug, title, body, description, image]
76 |
77 | delete:
78 | Delete an existing post
79 |
80 | parameters = [slug]
81 | """
82 |
83 | queryset = Post.objects.all()
84 | lookup_field = "slug"
85 | serializer_class = PostDetailSerializer
86 | permission_classes = [IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly]
87 |
88 |
89 | class CreateCommentAPIView(APIView):
90 | """
91 | post:
92 | Create a comment instnace. Returns created comment data
93 |
94 | parameters: [slug, body]
95 |
96 | """
97 |
98 | serializer_class = CommentCreateUpdateSerializer
99 | permission_classes = [IsAuthenticated]
100 |
101 | def post(self, request, slug, *args, **kwargs):
102 | post = get_object_or_404(Post, slug=slug)
103 | serializer = CommentCreateUpdateSerializer(data=request.data)
104 | if serializer.is_valid(raise_exception=True):
105 | serializer.save(author=request.user, parent=post)
106 | return Response(serializer.data, status=200)
107 | else:
108 | return Response({"errors": serializer.errors}, status=400)
109 |
110 |
111 | class ListCommentAPIView(APIView):
112 | """
113 | get:
114 | Returns the list of comments on a particular post
115 | """
116 |
117 | permission_classes = [IsAuthenticatedOrReadOnly]
118 |
119 | def get(self, request, slug):
120 | post = Post.objects.get(slug=slug)
121 | comments = Comment.objects.filter(parent=post)
122 | serializer = CommentSerializer(comments, many=True)
123 | return Response(serializer.data, status=200)
124 |
125 |
126 | class DetailCommentAPIView(MultipleFieldLookupMixin, RetrieveUpdateDestroyAPIView):
127 | """
128 | get:
129 | Returns the details of a comment instance. Searches comment using comment id and post slug in the url.
130 |
131 | put:
132 | Updates an existing comment. Returns updated comment data
133 |
134 | parameters: [parent, author, body]
135 |
136 | delete:
137 | Delete an existing comment
138 |
139 | parameters: [parent, author, body]
140 | """
141 |
142 | permission_classes = [IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly]
143 |
144 | queryset = Comment.objects.all()
145 | lookup_fields = ["parent", "id"]
146 | serializer_class = CommentCreateUpdateSerializer
--------------------------------------------------------------------------------
/backend/README.md:
--------------------------------------------------------------------------------
1 | # Blog API Backend
2 |
3 | A REST API for blog built using Django Rest Framework
4 |
5 | ## Installation
6 |
7 | ### Requirements
8 | - Python
9 | - Django
10 |
11 | Complete list available in requirements.txt file
12 |
13 | ### Quickstart
14 | - Clone the repo.
15 | ```bash
16 | git clone https://github.com/ExpressHermes/Blog-API.git
17 | ```
18 |
19 | - Inside the backend folder, make a virtual environment and activate it
20 | ```bash
21 | cd Blog-API/backend
22 | python -m venv env
23 | source env/bin/activate
24 | ```
25 |
26 | - Install requirements from requirements.txt
27 | ```
28 | pip install -r requirements.txt
29 | ```
30 |
31 | - Makemigrations and migrate the project
32 | ```
33 | python manage.py makemigrations && python manage.py migrate
34 | ```
35 |
36 | - Create a superuser
37 | ```
38 | python manage.py createsuperuser
39 | ```
40 |
41 | - Runserver
42 | ```
43 | python manage.py runserver
44 | ```
45 |
46 | **Note: After running the server, you can use the api inside browser or you can use Postman to make api calls. Make sure in each api call, you provide username, password by creating a user.**
47 |
48 | # RESTAPI Docs
49 | I have added `drf-yasg` for API documentation which can be accessed after running the backend server and going to following links:
50 |
51 | Swagger UI docs: http://127.0.0.1:8000/swagger/
52 |
53 | Redoc UI docs: http://127.0.0.1:8000/redoc/
54 |
55 | While working with api in browser, you can login using `http://127.0.0.1:8000/api-auth/` link.
56 |
57 |
58 | ## API
59 |
60 | User model
61 |
62 | - User:
63 | - username: string(unique),
64 | - email: email,
65 | - password: string(min 8 chars)
66 |
67 |
68 |
69 |
70 | Post Model
71 |
72 | - Post:
73 | - id: Post id(read only),
74 | - slug: string,
75 | - title: string,
76 | - author: user-id(read only),
77 | - body: string,
78 | - description: string,
79 | - image: image(optional)
80 | - created_at: datetime(read only)
81 | - updated_at: datetime(read only)
82 |
83 |
84 |
85 | Comment Model
86 |
87 | - Comment:
88 | - parent: post id(read only),
89 | - author: user id(ready only),
90 | - body: string,
91 | - created_at: datetime(read only)
92 | - updated_at: datetime(read only)
93 |
94 |
95 |
96 |
97 | ### Endpoints
98 |
99 | Brief explanation of endpoints:
100 |
101 | | Function | REQUEST | Endpoint | Authorization | form-data |
102 | |--------------------------------------------------------------------------------------------------------|------------|---------------------------------------------------------|---------------|-------------------------------------------|
103 | | Create new user | POST | http://127.0.0.1:8000/user/register/ | Not Required | username, email, password |
104 | | Returns list of all existing users | GET | http://127.0.0.1:8000/user/ | Basic Auth | |
105 | | Returns the detail of an user instance | GET | http://127.0.0.1:8000/user/{int:id}/ | Basic Auth | |
106 | | Update the detail of an user instance | PUT, PATCH | http://127.0.0.1:8000/user/{int:id}/ | Basic Auth | |
107 | | Delete an user instance | DELETE | http://127.0.0.1:8000/user/{int:id}/ | Basic Auth | |
108 | | | | | | |
109 | | Returns a list of all existing posts | GET | http://127.0.0.1:8000/posts/ | Not Required | |
110 | | Creates a new post instance. Returns created post data | POST | http://127.0.0.1:8000/posts/create/ | Basic Auth | title, body, description, image: optional |
111 | | Returns the details of a post instance. Searches post using slug field. | GET | http://127.0.0.1:8000/posts/{str:slug}/ | Basic Auth | |
112 | | Updates an existing post. Returns updated post data | PUT, PATCH | http://127.0.0.1:8000/posts/{str:slug}/ | Basic Auth | title, body, description, image: optional |
113 | | Deletes the existing post | DELETE | http://127.0.0.1:8000/posts/{str:slug}/ | Basic Auth | |
114 | | Returns the list of comments on a particular post | GET | http://127.0.0.1:8000/posts/{str:slug}/comment/ | Not Required | |
115 | | Create a comment instnace. Returns created comment data | POST | http://127.0.0.1:8000/posts/{str:slug}/comment/create/ | Basic Auth | body: comment body |
116 | | Returns the details of a comment instance. Searches comment using comment id and post slug in the url. | GET | http://127.0.0.1:8000/posts/{str:slug}/comment/{int:id}/ | Not Required | |
117 | | Updates an existing comment. Returns updated comment data | PUT, PATCH | http://127.0.0.1:8000/posts/{str:slug}/comment/{int:id}/ | Basic Auth | body: comment body |
118 | | Deletes an existing comment | DELETE | http://127.0.0.1:8000/posts/{str:slug}/comment/{int:id}/ | Basic Auth | body: comment body |
119 |
120 |
121 |
--------------------------------------------------------------------------------