├── frontend ├── src │ ├── App.css │ ├── Components │ │ ├── index.tsx │ │ └── Layout │ │ │ ├── Footer │ │ │ ├── index.tsx │ │ │ └── Footer.tsx │ │ │ ├── Header │ │ │ ├── index.tsx │ │ │ └── Navbar │ │ │ │ ├── index.tsx │ │ │ │ └── Navbar.tsx │ │ │ ├── index.tsx │ │ │ └── Layout.tsx │ ├── tailwind.css │ ├── Pages │ │ ├── LoginPage │ │ │ ├── Components │ │ │ │ ├── index.tsx │ │ │ │ └── LoginForm.tsx │ │ │ ├── index.tsx │ │ │ └── LoginPage.tsx │ │ ├── RegisterPage │ │ │ ├── Components │ │ │ │ ├── index.tsx │ │ │ │ └── RegisterForm.tsx │ │ │ ├── index.tsx │ │ │ └── RegisterPage.tsx │ │ ├── FeedPage │ │ │ ├── index.tsx │ │ │ ├── Components │ │ │ │ ├── index.tsx │ │ │ │ ├── Posts │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── PostCreation.tsx │ │ │ │ │ ├── Posts.tsx │ │ │ │ │ └── Post.tsx │ │ │ │ ├── Replies │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── ReplyCreation.tsx │ │ │ │ │ ├── Replies.tsx │ │ │ │ │ └── Reply.tsx │ │ │ │ └── Comments │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── CommentCreation.tsx │ │ │ │ │ ├── Comments.tsx │ │ │ │ │ └── Comment.tsx │ │ │ └── FeedPage.tsx │ │ └── index.tsx │ ├── config.tsx │ ├── setupTests.ts │ ├── App.test.tsx │ ├── relay │ │ ├── environment.tsx │ │ └── fetchGraphQL.tsx │ ├── Services │ │ └── Subscriptions │ │ │ ├── index.tsx │ │ │ ├── ReplyLikeSubscription.tsx │ │ │ ├── CommentLikeSubscription.tsx │ │ │ ├── PostLikeSubscription.tsx │ │ │ ├── Subscriptions.tsx │ │ │ ├── NewPostsSubscription.tsx │ │ │ ├── NewCommentsSubscription.tsx │ │ │ └── NewRepliesSubscription.tsx │ ├── index.tsx │ ├── routes.tsx │ ├── react-app-env.d.ts │ ├── App.tsx │ └── serviceWorker.ts ├── .dockerignore ├── public │ ├── robots.txt │ ├── favicon.ico │ ├── logo192.png │ ├── logo512.png │ ├── socialnetwork-post_example.gif │ ├── socialnetwork-register_example.gif │ ├── manifest.json │ └── index.html ├── types │ ├── babel-plugin-relay.d.ts │ └── react-router-dom.d.ts ├── .gitignore ├── README.md ├── tsconfig.json ├── tailwind.config.js ├── Dockerfile ├── package.json └── schema │ └── schema.graphql ├── backend ├── .gitignore ├── src │ ├── modules │ │ ├── users │ │ │ ├── mutations │ │ │ │ ├── index.ts │ │ │ │ ├── Login.ts │ │ │ │ └── CreateUser.ts │ │ │ ├── UserLoader.ts │ │ │ ├── UserType.ts │ │ │ └── UserModel.ts │ │ ├── posts │ │ │ ├── mutations │ │ │ │ ├── index.ts │ │ │ │ ├── PostCreation.ts │ │ │ │ └── LikePost.ts │ │ │ ├── subscriptions │ │ │ │ ├── index.ts │ │ │ │ ├── PostCreation.ts │ │ │ │ └── PostLike.ts │ │ │ ├── PostLoader.ts │ │ │ ├── PostModel.ts │ │ │ └── PostType.ts │ │ ├── reply │ │ │ ├── mutations │ │ │ │ ├── index.ts │ │ │ │ ├── CreateReply.ts │ │ │ │ └── LikeReply.ts │ │ │ ├── subscriptions │ │ │ │ ├── index.ts │ │ │ │ ├── ReplyLikeSubscription.ts │ │ │ │ └── ReplyCreationSubscription.ts │ │ │ ├── ReplyLoader.ts │ │ │ ├── ReplyModel.ts │ │ │ └── ReplyType.ts │ │ └── comments │ │ │ ├── mutations │ │ │ ├── index.ts │ │ │ ├── CreateComment.ts │ │ │ └── LikeComment.ts │ │ │ ├── subscriptions │ │ │ ├── index.ts │ │ │ ├── CommentLikeSubscription.ts │ │ │ └── CreateCommentSubscription.ts │ │ │ ├── CommentLoader.ts │ │ │ ├── CommentModel.ts │ │ │ └── CommentType.ts │ ├── schema │ │ ├── Schema.ts │ │ ├── SubscriptionType.ts │ │ ├── MutationType.ts │ │ └── QueryType.ts │ ├── auth.ts │ ├── graphql │ │ ├── NodeDefinitions.ts │ │ └── registeredTypes.ts │ ├── server.ts │ └── app.ts ├── tslint.json ├── .babelrc ├── README.md ├── tsconfig.json └── package.json └── README.md /frontend/src/App.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /dist 3 | .env -------------------------------------------------------------------------------- /frontend/src/Components/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './Layout' -------------------------------------------------------------------------------- /frontend/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | /build 4 | dockerBuild.log -------------------------------------------------------------------------------- /frontend/src/Components/Layout/Footer/index.tsx: -------------------------------------------------------------------------------- 1 | export { default } from './Footer'; -------------------------------------------------------------------------------- /frontend/src/Components/Layout/Header/index.tsx: -------------------------------------------------------------------------------- 1 | export { default } from './Navbar'; -------------------------------------------------------------------------------- /frontend/src/Components/Layout/Header/Navbar/index.tsx: -------------------------------------------------------------------------------- 1 | export { default } from './Navbar'; -------------------------------------------------------------------------------- /frontend/src/tailwind.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; -------------------------------------------------------------------------------- /frontend/src/Pages/LoginPage/Components/index.tsx: -------------------------------------------------------------------------------- 1 | export {default as LoginForm} from './LoginForm'; -------------------------------------------------------------------------------- /frontend/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /frontend/src/Pages/RegisterPage/Components/index.tsx: -------------------------------------------------------------------------------- 1 | export {default as RegisterForm} from './RegisterForm'; -------------------------------------------------------------------------------- /frontend/types/babel-plugin-relay.d.ts: -------------------------------------------------------------------------------- 1 | export = index; 2 | declare function index(context: any): any; 3 | -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Streeterxs/socialnetwork/HEAD/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Streeterxs/socialnetwork/HEAD/frontend/public/logo192.png -------------------------------------------------------------------------------- /frontend/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Streeterxs/socialnetwork/HEAD/frontend/public/logo512.png -------------------------------------------------------------------------------- /frontend/src/Pages/FeedPage/index.tsx: -------------------------------------------------------------------------------- 1 | export {default as FeedPage} from './FeedPage'; 2 | export * from './Components'; -------------------------------------------------------------------------------- /frontend/src/Pages/LoginPage/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './Components'; 2 | export {default as LoginPage} from './LoginPage'; -------------------------------------------------------------------------------- /frontend/src/Pages/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './LoginPage'; 2 | export * from './RegisterPage'; 3 | export * from './FeedPage'; -------------------------------------------------------------------------------- /frontend/src/Pages/RegisterPage/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './Components'; 2 | export { default as RegisterPage} from './RegisterPage'; -------------------------------------------------------------------------------- /frontend/src/config.tsx: -------------------------------------------------------------------------------- 1 | const config = { 2 | GRAPHQL_URL: process.env.REACT_APP_GRAPHQL_URL 3 | }; 4 | 5 | export default config; -------------------------------------------------------------------------------- /frontend/src/Pages/FeedPage/Components/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './Comments'; 2 | export * from './Posts'; 3 | export * from './Replies'; -------------------------------------------------------------------------------- /frontend/public/socialnetwork-post_example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Streeterxs/socialnetwork/HEAD/frontend/public/socialnetwork-post_example.gif -------------------------------------------------------------------------------- /frontend/public/socialnetwork-register_example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Streeterxs/socialnetwork/HEAD/frontend/public/socialnetwork-register_example.gif -------------------------------------------------------------------------------- /backend/src/modules/users/mutations/index.ts: -------------------------------------------------------------------------------- 1 | import CreateUser from './CreateUser'; 2 | import Login from './Login'; 3 | 4 | export default { 5 | CreateUser, 6 | Login 7 | } -------------------------------------------------------------------------------- /frontend/src/Components/Layout/index.tsx: -------------------------------------------------------------------------------- 1 | export { default as Footer } from './Footer'; 2 | export { default as Header} from './Header'; 3 | export { default as Layout } from './Layout'; -------------------------------------------------------------------------------- /backend/src/modules/posts/mutations/index.ts: -------------------------------------------------------------------------------- 1 | import PostCreation from './PostCreation'; 2 | import LikePost from './LikePost'; 3 | 4 | export default { 5 | PostCreation, 6 | LikePost 7 | }; -------------------------------------------------------------------------------- /backend/src/modules/reply/mutations/index.ts: -------------------------------------------------------------------------------- 1 | import CreateReply from './CreateReply'; 2 | import LikeReply from './LikeReply'; 3 | 4 | export default { 5 | CreateReply, 6 | LikeReply 7 | } -------------------------------------------------------------------------------- /frontend/src/Pages/FeedPage/Components/Posts/index.tsx: -------------------------------------------------------------------------------- 1 | export {default as Posts} from './Posts'; 2 | export {default as Post} from './Post'; 3 | export {default as PostCreation} from './PostCreation'; -------------------------------------------------------------------------------- /backend/src/modules/comments/mutations/index.ts: -------------------------------------------------------------------------------- 1 | import CreateComment from './CreateComment'; 2 | import LikeComment from './LikeComment' 3 | 4 | export default { 5 | CreateComment, 6 | LikeComment 7 | }; -------------------------------------------------------------------------------- /frontend/src/Pages/FeedPage/Components/Replies/index.tsx: -------------------------------------------------------------------------------- 1 | export {default as Replies} from './Replies'; 2 | export {default as Reply} from './Reply'; 3 | export {default as ReplyCreation} from './ReplyCreation'; -------------------------------------------------------------------------------- /frontend/src/Pages/FeedPage/Components/Comments/index.tsx: -------------------------------------------------------------------------------- 1 | export {default as Comments} from './Comments'; 2 | export {default as Comment} from './Comment'; 3 | export {default as CommentCreation} from './CommentCreation'; -------------------------------------------------------------------------------- /backend/src/modules/posts/subscriptions/index.ts: -------------------------------------------------------------------------------- 1 | import PostCreationSubscription from './PostCreation'; 2 | import PostLikeSubscription from './PostLike'; 3 | 4 | export default { 5 | PostCreationSubscription, 6 | PostLikeSubscription 7 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## About 2 | 3 | This is a social network made using a Graphql backend, MongoDB database and a Relay Web Client. 4 | 5 | ## How to contribute 6 | 7 | To contribute just create any issue or PR if you like. I will read all. Any doubts contact me if you want. 8 | -------------------------------------------------------------------------------- /backend/src/modules/reply/subscriptions/index.ts: -------------------------------------------------------------------------------- 1 | import ReplyCreationSubscription from './ReplyCreationSubscription'; 2 | import ReplyLikeSubscription from './ReplyLikeSubscription'; 3 | 4 | export default { 5 | ReplyCreationSubscription, 6 | ReplyLikeSubscription 7 | } -------------------------------------------------------------------------------- /frontend/src/setupTests.ts: -------------------------------------------------------------------------------- 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/extend-expect'; 6 | -------------------------------------------------------------------------------- /backend/src/modules/comments/subscriptions/index.ts: -------------------------------------------------------------------------------- 1 | import {default as CreateCommentSubscription} from './CreateCommentSubscription'; 2 | import {default as CommentLikeSubscription} from './CommentLikeSubscription'; 3 | 4 | export default { 5 | CreateCommentSubscription, 6 | CommentLikeSubscription 7 | } -------------------------------------------------------------------------------- /backend/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": [ 4 | "tslint:recommended" 5 | ], 6 | "jsRules": {}, 7 | "rules": { 8 | "trailing-comma": [ false ], 9 | "no-var-requires": false, 10 | "no-console": false 11 | }, 12 | "rulesDirectory": [] 13 | } -------------------------------------------------------------------------------- /backend/src/modules/reply/ReplyLoader.ts: -------------------------------------------------------------------------------- 1 | import Reply, {IReply} from './ReplyModel'; 2 | import Dataloader from 'dataloader'; 3 | 4 | const replyDataLoader = new Dataloader((keys: string[]) => Reply.find({_id: {$in: keys}})); 5 | 6 | export const replyLoader = async (id: string) => { 7 | return await replyDataLoader.load(id); 8 | } 9 | -------------------------------------------------------------------------------- /frontend/src/Components/Layout/Footer/Footer.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Footer = () => ( 4 |
5 | developed by Afonso Araújo Neto 6 |
7 | ); 8 | 9 | export default Footer; -------------------------------------------------------------------------------- /frontend/src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders learn react link', () => { 6 | const { getByText } = render(); 7 | const linkElement = getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /backend/.babelrc: -------------------------------------------------------------------------------- 1 | //.babelrc 2 | 3 | { 4 | "presets": [ 5 | "@babel/preset-env", 6 | "@babel/preset-typescript" 7 | ], 8 | "plugins": [ 9 | // https://github.com/parcel-bundler/parcel/issues/871#issuecomment-370135105 10 | // https://github.com/babel/babel-loader/issues/560#issuecomment-370180866 11 | "@babel/plugin-transform-runtime" 12 | ] 13 | } -------------------------------------------------------------------------------- /backend/src/schema/Schema.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLSchema } from "graphql"; 2 | 3 | import QueryType from "./QueryType"; 4 | import MutationType from "./MutationType"; 5 | import SubscriptionType from "./SubscriptionType"; 6 | 7 | 8 | export const Schema = new GraphQLSchema({ 9 | query: QueryType, 10 | mutation: MutationType, 11 | subscription: SubscriptionType 12 | }); -------------------------------------------------------------------------------- /backend/README.md: -------------------------------------------------------------------------------- 1 | ## Social Network Backend 2 | 3 | This is a backend GraphQL Api to serve solutions for an social network project. 4 | 5 | ### To run 6 | 7 | Firstle install dependencies using npm or yarn package manager `npm install` or `yarn install`. 8 | 9 | You must install a MongoDB environment and execute `mongod` to run it. 10 | 11 | Run backend using `npm start` on `yarn start` depending on your local environment. 12 | 13 | -------------------------------------------------------------------------------- /backend/src/auth.ts: -------------------------------------------------------------------------------- 1 | import User, { IUser } from "./modules/users/UserModel"; 2 | 3 | 4 | const getUser = async (token: string): Promise => { 5 | try { 6 | const user = await User.findByToken(token); 7 | if (user) { 8 | user.verifyAuthToken(); 9 | return user; 10 | } 11 | } catch(err) { 12 | return { user: null }; 13 | } 14 | } 15 | 16 | export default getUser; -------------------------------------------------------------------------------- /backend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "ES6", 4 | "esModuleInterop": true, 5 | "target": "es6", 6 | "noImplicitAny": true, 7 | "moduleResolution": "node", 8 | "sourceMap": true, 9 | "outDir": "dist", 10 | "baseUrl": ".", 11 | "paths": { 12 | "*": [ 13 | "node_modules/*" 14 | ] 15 | } 16 | }, 17 | "include": [ 18 | "src/**/*" 19 | ] 20 | } -------------------------------------------------------------------------------- /frontend/src/relay/environment.tsx: -------------------------------------------------------------------------------- 1 | import { Environment, Network, RecordSource, Store, SubscribeFunction } from 'relay-runtime'; 2 | import { fetchGraphQL, setupSubscription } from './fetchGraphQL'; 3 | 4 | 5 | const network = Network.create( 6 | fetchGraphQL, 7 | setupSubscription as SubscribeFunction 8 | ); 9 | // Export a singleton instance of Relay Environment configured with our network function: 10 | export default new Environment({ 11 | network, 12 | store: new Store(new RecordSource()), 13 | }); -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | 26 | # Graphql Generated 27 | __generated__ 28 | index.css 29 | 30 | # Docker Logs 31 | dockerBuild.log -------------------------------------------------------------------------------- /backend/src/schema/SubscriptionType.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLObjectType } from "graphql"; 2 | 3 | import PostSubscriptions from "../modules/posts/subscriptions/"; 4 | import CommentSubscriptions from '../modules/comments/subscriptions' 5 | import ReplySubscriptions from "../modules/reply/subscriptions"; 6 | 7 | 8 | const SubscriptionType = new GraphQLObjectType({ 9 | name: 'SubscriptionType', 10 | fields: () => ({ 11 | ...PostSubscriptions, 12 | ...CommentSubscriptions, 13 | ...ReplySubscriptions 14 | }) 15 | }); 16 | 17 | export default SubscriptionType -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | ## Social Network Frontend 2 | 3 | This is a Relay client to consume the backend graphql api. 4 | 5 | ## To Run 6 | 7 | Execute `yarn install` or `npm install` depending on your local package manager. To install all dependencies 8 | 9 | Execute `yarn start` or `npm start` depending on your local package manager. To run the frontend web client. 10 | 11 | ## Examples 12 | 13 | ### User creation and login 14 | 15 | ![](./public/socialnetwork-register_example.gif) 16 | 17 | ### Post creation and reply 18 | 19 | ![](./public/socialnetwork-post_example.gif) 20 | -------------------------------------------------------------------------------- /frontend/src/Services/Subscriptions/index.tsx: -------------------------------------------------------------------------------- 1 | export {default as NewPostsSubscriptionModule} from './NewPostsSubscription'; 2 | export {default as PostLikeSubscriptionModule} from './PostLikeSubscription'; 3 | 4 | export {default as NewCommentsSubscriptionModule} from './NewCommentsSubscription'; 5 | export {default as CommentLikeSubscriptionModule} from './CommentLikeSubscription'; 6 | 7 | export {default as ReplyLikeSubscriptionModule} from './ReplyLikeSubscription'; 8 | export {default as NewRepliesSubscriptionModule} from './NewRepliesSubscription'; 9 | 10 | export {default} from './Subscriptions'; -------------------------------------------------------------------------------- /frontend/src/Components/Layout/Layout.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | 3 | import {Header, Footer} from './'; 4 | 5 | const Layout = ({children, userIsLogged, handleLogoutLogin}: any) => { 6 | 7 | return ( 8 |
9 |
10 |
11 | {children} 12 |
13 |
14 |
15 | ) 16 | } 17 | 18 | export default Layout -------------------------------------------------------------------------------- /frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 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/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.unstable_createRoot( 8 | document.getElementById('root') as HTMLElement 9 | ).render( 10 | 11 | 12 | 13 | ) 14 | 15 | // If you want your app to work offline and load faster, you can change 16 | // unregister() to register() below. Note this comes with some pitfalls. 17 | // Learn more about service workers: https://bit.ly/CRA-PWA 18 | serviceWorker.unregister(); 19 | -------------------------------------------------------------------------------- /backend/src/schema/MutationType.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLObjectType } from "graphql"; 2 | import UserMutations from "../modules/users/mutations"; 3 | import PostMutation from "../modules/posts/mutations"; 4 | import CommentMutations from '../modules/comments/mutations'; 5 | import ReplyMutations from '../modules/reply/mutations'; 6 | 7 | const MutationType = new GraphQLObjectType({ 8 | name: 'MutationType', 9 | description: 'Mutation Type', 10 | fields: () => ({ 11 | ...UserMutations, 12 | ...PostMutation, 13 | ...CommentMutations, 14 | ...ReplyMutations 15 | }) 16 | }); 17 | 18 | export default MutationType; -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "target": "es5", 5 | "lib": [ 6 | "dom", 7 | "dom.iterable", 8 | "esnext" 9 | ], 10 | "allowJs": true, 11 | "skipLibCheck": true, 12 | "esModuleInterop": true, 13 | "allowSyntheticDefaultImports": true, 14 | "strict": true, 15 | "forceConsistentCasingInFileNames": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /backend/src/graphql/NodeDefinitions.ts: -------------------------------------------------------------------------------- 1 | import { nodeDefinitions, fromGlobalId } from "graphql-relay"; 2 | import registeredTypes from "./registeredTypes"; 3 | 4 | export const {nodeInterface, nodeField, nodesField} = nodeDefinitions( 5 | (globalId) => { 6 | const {type, id} = fromGlobalId(globalId); 7 | const registeredType = registeredTypes.find(x => { 8 | return type === x.name 9 | }); 10 | return registeredType.loader(id); 11 | }, 12 | (obj) => { 13 | const registeredType = registeredTypes.find(x => obj instanceof x.dbType); 14 | if (registeredType) return registeredType.qlType 15 | return null; 16 | } 17 | ); -------------------------------------------------------------------------------- /frontend/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | theme: { 3 | extend: {}, 4 | boxShadow: { 5 | default: '0 1px 3px 0 rgba(0, 0, 0, .1), 0 1px 2px 0 rgba(0, 0, 0, .06)', 6 | md: '0 4px 6px -1px rgba(0, 0, 0, .1), 0 2px 4px -1px rgba(0, 0, 0, .06)', 7 | lg: '0 10px 15px -3px rgba(0, 0, 0, .1), 0 4px 6px -2px rgba(0, 0, 0, .05)', 8 | xl: '0 20px 25px -5px rgba(0, 0, 0, .1), 0 10px 10px -5px rgba(0, 0, 0, .04)', 9 | inner: 'inset 0 2px 4px 0 rgba(0, 0, 0, 0.06)', 10 | outline: '0 0 0 3px rgba(66, 153, 225, 0.5)', 11 | focus: '0 0 0 3px rgba(66, 153, 225, 0.5)', 12 | custom: '0 5px 20px rgba(0, 0, 0, 0.2)' 13 | } 14 | }, 15 | variants: {}, 16 | plugins: [], 17 | } 18 | -------------------------------------------------------------------------------- /backend/src/modules/reply/ReplyModel.ts: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | 3 | export interface IReply extends mongoose.Document { 4 | author: string; 5 | content: string; 6 | createdAt: Date; 7 | updatedAt: Date; 8 | likes: string[]; 9 | } 10 | 11 | const replySchema = new mongoose.Schema({ 12 | author: { 13 | type: mongoose.Schema.Types.ObjectId, 14 | ref: 'User', 15 | required: true 16 | }, 17 | content: { 18 | type: String, 19 | required: true 20 | }, 21 | likes: [{ 22 | type: mongoose.Schema.Types.ObjectId, 23 | ref: 'User' 24 | }] 25 | }, { 26 | timestamps: true 27 | }); 28 | 29 | const Reply = mongoose.model('Reply_SocialNetwork', replySchema); 30 | 31 | export default Reply -------------------------------------------------------------------------------- /frontend/src/routes.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { BrowserRouter, Route, Switch} from 'react-router-dom'; 3 | import { LoginPage, RegisterPage, FeedPage } from './Pages' 4 | 5 | const routes = ({userIsLogged, setUserIsLogged}: { 6 | userIsLogged: boolean, 7 | setUserIsLogged: (userIsLogged: boolean) => void 8 | }) => ( 9 | 10 | }/> 11 | }/> 12 | 13 | {/* 14 | 15 | */} 16 | 17 | ) 18 | 19 | export default routes; -------------------------------------------------------------------------------- /frontend/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | declare module 'babel-plugin-relay/macro' { 5 | export { graphql as default} from 'react-relay' 6 | } 7 | declare module 'react-relay/lib/relay-experimental' { 8 | export { 9 | EntryPointContainer, 10 | LazyLoadEntryPointContainer_DEPRECATED, 11 | MatchContainer, 12 | ProfilerContext, 13 | RelayEnvironmentProvider, 14 | PreloadableQueryRegistry, 15 | fetchQuery, 16 | preloadQuery, 17 | prepareEntryPoint, 18 | useBlockingPaginationFragment, 19 | useFragment, 20 | useLazyLoadQuery, 21 | useMutation, 22 | usePaginationFragment, 23 | usePreloadedQuery, 24 | useRefetchableFragment, 25 | useRelayEnvironment, 26 | useSubscribeToInvalidationState 27 | } from 'react-relay/lib/relay-experimental'; 28 | } -------------------------------------------------------------------------------- /backend/src/modules/comments/CommentLoader.ts: -------------------------------------------------------------------------------- 1 | import Comment, {IComment} from './CommentModel'; 2 | import Dataloader from 'dataloader'; 3 | 4 | 5 | const commentDataLoader = new Dataloader((keys: string[]) => Comment.find({_id: {$in: keys}})); 6 | const commentByReplyDataLoader = new Dataloader((keys: string[]) => Comment.find({replies: {$in: keys}})); 7 | 8 | export const commentLoader = async (id: string) => { 9 | const commentFounded = await commentDataLoader.load(id); 10 | console.log('comment founded by dataloader: ', commentFounded); 11 | return commentFounded; 12 | } 13 | 14 | export const commentLoaderByReply = async (replyId: string) => { 15 | const commentFounded = await commentByReplyDataLoader.load(replyId); 16 | return commentFounded; 17 | } 18 | 19 | // TODO implements DataLoader for this 20 | export const commentsFromPostLoader = async (postId: string) => { 21 | const comments = Comment.findCommentsForPost(postId); 22 | return comments 23 | } -------------------------------------------------------------------------------- /backend/src/modules/users/UserLoader.ts: -------------------------------------------------------------------------------- 1 | import userModel, { IUser } from './UserModel'; 2 | import Dataloader from 'dataloader'; 3 | 4 | console.log('load user module'); 5 | 6 | const userLoader = new Dataloader((keys: string[]) => userModel.find({_id: {$in: keys}})); 7 | 8 | const loadUser = async (id: string) => { 9 | 10 | console.log('loaduser id: ', id); 11 | const user = await userLoader.load(id); 12 | 13 | console.log('user by dataloader: ', user); 14 | return user; 15 | } 16 | 17 | const userIdLoader = (user: IUser, field: keyof IUser) => { 18 | return field === 'tokens' ? user.tokens[0].token : user[field]; 19 | } 20 | 21 | const loadLoggedUser = async (token: string) => { 22 | console.log('loggeduser token: ', token); 23 | 24 | const user = await userModel.find({tokens: [{token}]}); 25 | 26 | console.log('logged user by dataloader: ', user); 27 | return user; 28 | } 29 | 30 | export { loadUser, loadLoggedUser, userLoader, userIdLoader }; -------------------------------------------------------------------------------- /frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | # socialnetwork:frontDev 2 | FROM node:16-alpine as development 3 | 4 | RUN --mount=type=bind,source=/package.json,target=/package.json \ 5 | --mount=type=bind,source=/package-lock.json,target=/package-lock.json \ 6 | npm ci 7 | 8 | CMD npm run start 9 | 10 | FROM node:16-alpine as setup 11 | 12 | WORKDIR /socialnetwork 13 | 14 | COPY . . 15 | 16 | ENV REACT_APP_GRAPHQL_URL=http://localhost:3332/graphql 17 | 18 | # update first from experimental versions to remove --force 19 | RUN npm ci --force 20 | RUN npm run build 21 | 22 | # socialnetwork:frontProd 23 | FROM node:16-alpine as production 24 | 25 | WORKDIR /socialnetwork 26 | 27 | RUN --mount=type=bind,from=setup \ 28 | npm version 29 | 30 | RUN --mount=type=bind,from=setup \ 31 | cd socialnetwork && ls 32 | 33 | RUN --mount=type=bind,from=setup \ 34 | ls -a 35 | 36 | COPY --from=setup ./socialnetwork/build ./ 37 | 38 | RUN npm install --global http-server 39 | 40 | EXPOSE 3100 41 | CMD http-server . -o -p 3100 -------------------------------------------------------------------------------- /backend/src/modules/users/mutations/Login.ts: -------------------------------------------------------------------------------- 1 | import { mutationWithClientMutationId } from "graphql-relay"; 2 | import { GraphQLString, graphql } from "graphql"; 3 | import userType from "../UserType"; 4 | import User from "../UserModel"; 5 | 6 | 7 | const mutation = mutationWithClientMutationId({ 8 | name: 'Login', 9 | description: 'Login a user, generates new token', 10 | inputFields: { 11 | email: { 12 | type: GraphQLString 13 | }, 14 | password: { 15 | type: GraphQLString 16 | } 17 | }, 18 | outputFields: { 19 | user: { 20 | type: userType, 21 | resolve: (user) => user 22 | } 23 | }, 24 | mutateAndGetPayload: async ({email, password}) => { 25 | try { 26 | const user = await User.findByCredentials(email, password); 27 | const token = await user.generateAuthToken(); 28 | return user; 29 | } catch (err) { 30 | console.log('entrou erro catch'); 31 | console.log(err); 32 | } 33 | } 34 | }); 35 | 36 | export default mutation; -------------------------------------------------------------------------------- /backend/src/graphql/registeredTypes.ts: -------------------------------------------------------------------------------- 1 | import { loadUser } from '../modules/users/UserLoader'; 2 | import { postLoader } from '../modules/posts/PostLoader'; 3 | import { commentLoader } from '../modules/comments/CommentLoader'; 4 | import { replyLoader } from '../modules/reply/ReplyLoader'; 5 | import User from '../modules/users/UserModel'; 6 | import Post from '../modules/posts/PostModel'; 7 | import Reply from '../modules/reply/ReplyModel'; 8 | import Comment from '../modules/comments/CommentModel'; 9 | 10 | const registeredTypes = [ 11 | { 12 | name: 'User', 13 | qlType: 'UserType', 14 | dbType: User, 15 | loader: loadUser 16 | }, 17 | { 18 | name: 'Post', 19 | qlType: 'PostType', 20 | dbType: Post, 21 | loader: postLoader 22 | }, 23 | { 24 | name: 'Comment', 25 | qlType: 'CommentType', 26 | dbType: Comment, 27 | loader: commentLoader 28 | }, 29 | { 30 | name: 'Reply', 31 | qlType: 'ReplyType', 32 | dbType: Reply, 33 | loader: replyLoader 34 | } 35 | ] 36 | 37 | export default registeredTypes; -------------------------------------------------------------------------------- /backend/src/server.ts: -------------------------------------------------------------------------------- 1 | import App from './app.js'; 2 | import getUser from './auth.js'; 3 | import { execute, subscribe } from 'graphql'; 4 | 5 | import { SubscriptionServer } from 'subscriptions-transport-ws'; 6 | import { createServer } from 'http'; 7 | import { Schema } from './schema/Schema'; 8 | 9 | type ConnectionParams = { 10 | authorization?: string; 11 | }; 12 | 13 | (async () => { 14 | const server = createServer(App.callback()); 15 | 16 | server.listen(process.env.PORT ?? '3333', () => { 17 | console.log('O servidor foi iniciado'); 18 | }); 19 | 20 | const subscriptionServer = SubscriptionServer.create( 21 | { 22 | onConnect: async (connectionParams: ConnectionParams) => { 23 | const user = await getUser(connectionParams.authorization); 24 | return { 25 | req: {}, 26 | user 27 | } 28 | }, 29 | // eslint-disable-next-line 30 | onDisconnect: () => console.log('Client subscription disconnected!'), 31 | execute, 32 | subscribe, 33 | schema: Schema, 34 | }, 35 | { 36 | server, 37 | path: '/subscriptions', 38 | }, 39 | ); 40 | })(); -------------------------------------------------------------------------------- /backend/src/modules/users/mutations/CreateUser.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLString } from "graphql"; 2 | 3 | import userType from "../UserType"; 4 | import userModel from "../UserModel"; 5 | import { mutationWithClientMutationId } from "graphql-relay"; 6 | import { loadUser } from "../UserLoader"; 7 | 8 | export const mutation = mutationWithClientMutationId({ 9 | name: 'UserCreation', 10 | description: 'Create new user', 11 | inputFields: { 12 | name: { 13 | type: GraphQLString 14 | }, 15 | password: { 16 | type: GraphQLString 17 | }, 18 | email: { 19 | type: GraphQLString 20 | } 21 | }, 22 | outputFields: { 23 | user: { 24 | type: userType, 25 | resolve: async (user) => await loadUser(user.id) 26 | } 27 | }, 28 | mutateAndGetPayload: async ({name, password, email}) => { 29 | try { 30 | const newUser = new userModel({name, password, email}); 31 | const returnNewUser = await newUser.save(); 32 | return returnNewUser; 33 | } catch (err) { 34 | console.log(err) 35 | return err; 36 | } 37 | } 38 | }); 39 | 40 | export default mutation; -------------------------------------------------------------------------------- /frontend/src/Pages/FeedPage/Components/Replies/ReplyCreation.tsx: -------------------------------------------------------------------------------- 1 | import React, { unstable_useTransition as useTransition } from 'react'; 2 | 3 | const ReplyCreation = ({formSubmit, replyContentChange}: { 4 | formSubmit: (event: React.FormEvent) => void, 5 | replyContentChange: (event: string) => void 6 | }) => { 7 | const [startTransition, isPending] = useTransition({ 8 | timeoutMs: 10000 9 | }); 10 | 11 | const replyCreationSubmitTransition = (event: React.FormEvent) => { 12 | startTransition(() => { 13 | formSubmit(event); 14 | }) 15 | }; 16 | return( 17 |
18 | replyContentChange(event.target.value)}/> 23 | 24 | {isPending ? 'loading...' : null} 25 |
26 | ); 27 | }; 28 | 29 | export default ReplyCreation; -------------------------------------------------------------------------------- /backend/src/schema/QueryType.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLObjectType, GraphQLString, GraphQLList, GraphQLNonNull } from 'graphql'; 2 | 3 | import { postsLoaderByAuthors } from '../modules/posts/PostLoader'; 4 | import userType from '../modules/users/UserType'; 5 | import { nodeField } from '../graphql/NodeDefinitions'; 6 | import { nodesField } from '../graphql/NodeDefinitions'; 7 | import { PostConnection } from '../modules/posts/PostType'; 8 | import { connectionArgs, connectionFromArray } from 'graphql-relay'; 9 | 10 | 11 | const QueryType = new GraphQLObjectType({ 12 | name: 'Query', 13 | description: 'General QueryType', 14 | fields: () => ({ 15 | node: nodeField, 16 | nodes: nodesField, 17 | myself: { 18 | type: userType, 19 | resolve: (value, args, {user}) => { 20 | return user ? user : null; 21 | } 22 | }, 23 | myPosts: { 24 | type: PostConnection, 25 | args: connectionArgs, 26 | resolve: async (value, args, context) => { 27 | return connectionFromArray( 28 | await postsLoaderByAuthors([context.user.id, ...context.user.friends]), 29 | args 30 | ) 31 | } 32 | }}) 33 | }); 34 | 35 | export default QueryType; -------------------------------------------------------------------------------- /backend/src/modules/comments/CommentModel.ts: -------------------------------------------------------------------------------- 1 | import mongoose, { mongo } from 'mongoose'; 2 | 3 | export interface IComment extends mongoose.Document { 4 | author: string, 5 | content: string, 6 | likes: string[], 7 | createdAt: Date, 8 | updatedAt: Date, 9 | replies: string[] 10 | } 11 | 12 | export interface ICommentModel extends mongoose.Model { 13 | findCommentsForPost(postId: string): IComment[] 14 | } 15 | 16 | const commentSchema = new mongoose.Schema({ 17 | author: { 18 | type: mongoose.Schema.Types.ObjectId, 19 | ref: 'User', 20 | required: true 21 | }, 22 | content: { 23 | type: String, 24 | required: true 25 | }, 26 | likes: [{ 27 | type: mongoose.Schema.Types.ObjectId, 28 | ref: 'User' 29 | }], 30 | replies: [{ 31 | type: mongoose.Schema.Types.ObjectId, 32 | ref: 'Reply' 33 | }] 34 | }, { 35 | timestamps: true 36 | }); 37 | 38 | commentSchema.statics.findCommentsForPost = async (postId: string) => { 39 | const commentsOfPost = await Comment.find({post: postId}).sort({createdAt: 1}); 40 | return commentsOfPost; 41 | }; 42 | 43 | const Comment = mongoose.model('Comment_SocialNetwork', commentSchema); 44 | 45 | export default Comment; 46 | -------------------------------------------------------------------------------- /backend/src/modules/posts/subscriptions/PostCreation.ts: -------------------------------------------------------------------------------- 1 | import { subscriptionWithClientId } from 'graphql-relay-subscription'; 2 | import { withFilter } from 'graphql-subscriptions'; 3 | 4 | import PostType from '../PostType'; 5 | import { postLoader } from '../PostLoader'; 6 | import { IPost } from '../PostModel'; 7 | import { pubsub } from '../../../app'; 8 | import { loadUser } from '../../users/UserLoader'; 9 | 10 | const PostCreationSubscription = subscriptionWithClientId({ 11 | name: 'PostCreationSubscription', 12 | inputFields: {}, 13 | outputFields: { 14 | post: { 15 | type: PostType, 16 | resolve: async (post: IPost, _: any, context: any) => await postLoader(post.id) 17 | } 18 | }, 19 | subscribe: withFilter( 20 | (input: any, context: any) => { 21 | return pubsub.asyncIterator('newPost'); 22 | }, 23 | async (postCreated: IPost, variables: any) => { 24 | const loggedUser = variables.user; 25 | const author = await loadUser(postCreated.author); 26 | 27 | return `${loggedUser._id}` === `${author._id}` || !!author.friends.includes(loggedUser._id); 28 | } 29 | ), 30 | getPayload: async (obj: any) => ({ 31 | id: obj.id 32 | }) 33 | }); 34 | 35 | export default PostCreationSubscription; -------------------------------------------------------------------------------- /backend/src/modules/posts/mutations/PostCreation.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLObjectType, GraphQLString } from 'graphql'; 2 | import { mutationWithClientMutationId } from 'graphql-relay'; 3 | 4 | import PostType from '../PostType'; 5 | import Post from '../PostModel'; 6 | import { IUser } from '../../../modules/users/UserModel'; 7 | import { pubsub } from '../../../app'; 8 | import { postLoader } from '../PostLoader'; 9 | 10 | const PostCreation = mutationWithClientMutationId({ 11 | name: 'PostCreation', 12 | description: 'Post Creation', 13 | inputFields: { 14 | content: { 15 | type: GraphQLString 16 | } 17 | }, 18 | outputFields: { 19 | post: { 20 | type: PostType, 21 | resolve: async (post) => await postLoader(post.id) 22 | } 23 | }, 24 | mutateAndGetPayload: async ({content}, {user}: {user: IUser}) => { 25 | try { 26 | 27 | const postCreated = new Post({content, author: `${user.id}`}); 28 | await postCreated.save(); 29 | 30 | user.posts.push(`${postCreated.id}`); 31 | await user.save(); 32 | 33 | pubsub.publish('newPost', postCreated); 34 | 35 | return postCreated; 36 | } catch (err) { 37 | console.log(err); 38 | } 39 | } 40 | }); 41 | 42 | export default PostCreation; -------------------------------------------------------------------------------- /backend/src/modules/posts/subscriptions/PostLike.ts: -------------------------------------------------------------------------------- 1 | import { subscriptionWithClientId } from "graphql-relay-subscription"; 2 | import { withFilter } from "graphql-subscriptions"; 3 | 4 | import PostType from "../PostType"; 5 | import { pubsub } from "../../../app"; 6 | import { IPost } from "../PostModel"; 7 | import { postLoader } from "../PostLoader"; 8 | import { loadUser } from "../../users/UserLoader"; 9 | 10 | const PostLikeSubscription = subscriptionWithClientId({ 11 | name: 'PostLikeSubscription', 12 | description: 'Post Like subscription', 13 | inputFields: {}, 14 | outputFields: { 15 | post: { 16 | type: PostType, 17 | resolve: (post: IPost) => postLoader(post.id) 18 | } 19 | }, 20 | subscribe: withFilter( 21 | (input: any, context: any) => { 22 | return pubsub.asyncIterator('postLike'); 23 | }, 24 | async (postLiked: IPost, variables: any) => { 25 | const loggedUser = variables.user; 26 | const author = await loadUser(postLiked.author); 27 | 28 | return `${loggedUser._id}` === `${author._id}` || !!author.friends.includes(loggedUser._id); 29 | } 30 | ), 31 | getPayload: (obj: any) => { 32 | return { 33 | id: obj.id 34 | } 35 | } 36 | }); 37 | 38 | export default PostLikeSubscription -------------------------------------------------------------------------------- /frontend/src/Pages/FeedPage/Components/Posts/PostCreation.tsx: -------------------------------------------------------------------------------- 1 | import React, { unstable_useTransition as useTransition } from 'react'; 2 | 3 | const PostCreation = ({contentChange, formSubmit}: 4 | { 5 | contentChange: (content: string) => void, 6 | formSubmit: (event: React.FormEvent) => void 7 | } 8 | ) => { 9 | 10 | const [startTransition, isPending] = useTransition(); 11 | 12 | const postSubmitTransition = (event: React.FormEvent) => { 13 | startTransition(() => { 14 | formSubmit(event); 15 | }) 16 | } 17 | 18 | return ( 19 |
20 |