├── .babelrc ├── .github ├── FUNDING.yml └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── LICENSE ├── README.md ├── dione ├── __init__.py ├── admin.py ├── apps.py ├── models.py ├── src │ ├── components │ │ ├── Card.js │ │ ├── CardLists.js │ │ ├── CardTopUsers.js │ │ ├── Context.js │ │ ├── Emoji.js │ │ ├── EmojiFilter.js │ │ ├── FollowButton.js │ │ ├── HomePage.js │ │ ├── HomePosts.js │ │ ├── List.js │ │ ├── Menu.js │ │ ├── MenuProfile.js │ │ ├── PostService.js │ │ ├── Posts.js │ │ ├── PostsEmoji.js │ │ ├── Postv2.js │ │ ├── RePost.js │ │ ├── RouteHandler.js │ │ ├── Routes.js │ │ ├── SearchText.js │ │ ├── Submit.js │ │ ├── SubmitText.js │ │ ├── UserFeed.js │ │ ├── UserFollowing.js │ │ ├── UserProfile.js │ │ ├── UserService.js │ │ ├── View.js │ │ ├── Vote.js │ │ └── withAuth.js │ ├── index.js │ └── main.scss ├── static │ └── img │ │ ├── 1f603.svg │ │ ├── apple-touch-icon-114x114.png │ │ ├── apple-touch-icon-144x144.png │ │ ├── apple-touch-icon-57x57.png │ │ ├── apple-touch-icon-72x72.png │ │ ├── big.pegao-w.svg │ │ ├── big.pegao.svg │ │ ├── eye.svg │ │ ├── favicon-16x16.png │ │ ├── favicon-196x196.png │ │ ├── favicon-32x32.png │ │ ├── favicon-96x96.png │ │ ├── favicon.ico │ │ ├── globe.svg │ │ ├── heart.svg │ │ ├── howtopost.gif │ │ ├── repegao.svg │ │ ├── repegar.svg │ │ ├── screen-first-list.png │ │ ├── screen-homepage.png │ │ ├── screen-submit.png │ │ ├── screen-wall.png │ │ ├── screen-wallv3.png │ │ ├── screen-wallv4.png │ │ └── share-alt.svg ├── templates │ ├── app │ │ └── index.html │ ├── articles │ │ ├── how-do-i-paste-a-link.html │ │ └── tour.html │ ├── index.html │ ├── privacy.html │ └── terms.html ├── tests.py ├── urls.py └── views.py ├── hiperion ├── __init__.py ├── admin.py ├── apps.py ├── models.py ├── pipeline.py ├── serializers.py ├── tests.py ├── urls.py └── views.py ├── manage.py ├── package-lock.json ├── package.json ├── requirements.txt ├── templates ├── base.html └── registration │ ├── logged_out.html │ └── login.html ├── voyager ├── __init__.py ├── registry.py ├── settings.py ├── sitemaps.py ├── urls.py └── wsgi.py ├── webpack-build-log.json └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", "@babel/preset-react" 4 | ], 5 | "plugins": [ 6 | "transform-class-properties" 7 | ] 8 | } -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | #github: zakokor 4 | patreon: zakokor 5 | ko_fi: zakokor 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Django # 2 | voyager/registry.py 3 | orbita 4 | dione/static/dist 5 | *.log 6 | *.pot 7 | *.pyc 8 | __pycache__ 9 | dione/migrations 10 | hiperion/migrations 11 | media 12 | sudo 13 | 14 | #node 15 | node_modules 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | [![Pegao](./dione/static/img/big.pegao.svg)](https://pegao.co) 4 | ### Pegao is a space to save and share curated pages with others. 5 | 6 |
7 | 8 | I'm developing Pegao with Django, ReactJS and Bulma, trying to keep it simple, and hosted on Digital Ocean. 9 | 10 | [Pegao.co](https://pegao.co) already works, but it is still in beta. So far I have built: 11 | - Post links. 12 | - Lists of links. 13 | - Followers. 14 | - Emoji filter. 15 | 16 | And the roapmap at a high level includes: 17 | - Likes. 18 | - Account page features. 19 | - Bookmark search. 20 | - Private bookmarks / private profiles. 21 | - Follow sources. 22 | - Share links in social networks, mail. 23 | - Page with most popular and most recent links. 24 | - Customize interests. 25 | - Spanish language support. 26 | - Order links and mark them as done. 27 | - Email notifications (possibly with Celery + Redis + Email service) 28 | 29 | ...and the list keeps growing, but we will need your help to get there. 30 | 31 | ## How did I start Pegao? 32 | I started the project a few months ago when I was looking for a bookmark where to organize all the links that remained open for a long time on my phone, easily save the new links I was finding and that I would need temporarily the next day; and also it had to be able to share some of those links with my wife without them ending up without being read...I know! There were many options but I did not feel that they were made to fulfill this purpose...and that's how I achieved my initial goal so far. 33 | 34 | ## Why Pegao? 35 | It comes from the spanish word "pegado" and means "to be fashionable" (like the song of the 90s), but it also refers to the crusted rice of the bottom of a cooking pot (delicious if it is not burned), and possibly in each country in Latin America has its own meaning because Pegao is something popular that belongs to people. 36 | 37 | I translated it into English as "Paste", but if someone has a better idea ... it would be very helpful. 38 | 39 | 40 | ## How to use... 41 | 42 | Pasting a link is very simple, write in the address bar of the browser "pegao.co" before the link you want to share and press "Paste". 43 | 44 |
45 | how to use... 46 |
47 | 48 |
49 | You can create your own lists —written with a / symbol— with your links to easily categorize them. 50 | 51 | ## Development 52 | 53 | ### Prerequisites 54 | 55 | - [PostgreSQL](https://www.enterprisedb.com/downloads/postgres-postgresql-downloads) 56 | - [Google OAuth 2.0 Client ID](https://console.developers.google.com/apis/credentials) 57 | 58 | 59 | ### Install dependencies 60 | 61 | ``` 62 | npm install 63 | pip install -r requirements.txt 64 | ``` 65 | 66 | ### Install database 67 | 68 | After installing PostgreSQL (if you don't already have it installed), update database config in `voyager\registry.py` with your values, eg. 69 | ``` 70 | REG_SECRET_KEY="" 71 | REG_DEBUG="True" 72 | REG_ALLOWED_HOSTS=["127.0.0.1"] 73 | REG_DATABASE_NAME="postgres" 74 | REG_DATABASE_USER="postgres" 75 | REG_DATABASE_PW="123" 76 | REG_DATABASE_HOST="localhost" 77 | BUGSNAG_API_KEY="" 78 | ``` 79 | 80 | #### Migrate database 81 | 82 | ``` 83 | python manage.py makemigrations hiperion 84 | python manage.py migrate 85 | ``` 86 | 87 | ### Set up Google OAuth 88 | 89 | This app uses sign-in with Google so you need to create your won [Google OAuth Client Credetials](https://developers.google.com/identity/protocols/oauth2/web-server). After creating your client update the following keys in `voyager\settings.py`: 90 | ``` 91 | SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = '' 92 | SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = '' 93 | ``` 94 | 95 | ### Start the application 96 | 97 | Build the front-end 98 | ``` 99 | npm run watch 100 | ``` 101 | 102 | Run the back-end 103 | ``` 104 | python manage.py runserver 105 | ``` 106 | The app should be running on `http://127.0.0.1:8000/`. 107 | 108 | 109 | ## Copyright 110 | 111 | [GNU Affero License](LICENSE) © 2019 [Gonzalo Aragón](https://github.com/zakokor). 112 | -------------------------------------------------------------------------------- /dione/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zakokor/pegao/9e4d0a2c9dff74b808eefa05cfbe2b9bbe2058a2/dione/__init__.py -------------------------------------------------------------------------------- /dione/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /dione/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class DioneConfig(AppConfig): 5 | name = 'dione' 6 | -------------------------------------------------------------------------------- /dione/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /dione/src/components/Card.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from "react"; 2 | import { Link } from 'react-router-dom'; 3 | import { AuthContext } from './Context'; 4 | import UserService from './UserService'; 5 | import FollowButton from './FollowButton'; 6 | 7 | const userService = new UserService(); 8 | const waitTime = 1000; 9 | 10 | class Card extends PureComponent { 11 | static contextType = AuthContext; 12 | 13 | constructor(props) { 14 | super(props); 15 | 16 | this.state = { data: null, }; 17 | } 18 | 19 | componentDidMount(){ 20 | const { match: { params } } = this.props; 21 | let currentUser = this.context.currentUser; 22 | 23 | console.log('Card params.username:'+params.username); 24 | console.log('Card currentUser:'+currentUser); 25 | if(params.username){ 26 | userService.getUser(params.username).then(res => { 27 | console.log('load getUser'); 28 | console.log(res); 29 | 30 | if(res.status=='200'){ 31 | const data = res.data; 32 | this.setState({ data: data }); 33 | } 34 | 35 | }).catch(()=>{ 36 | console.log('There was an error!'); 37 | }); 38 | }else if(currentUser){ 39 | this.setState({ data: currentUser }); 40 | } 41 | } 42 | 43 | render() { 44 | const {data} = this.state; 45 | let currentUser = this.context.currentUser; 46 | 47 | let divStyle = null; 48 | if(data) 49 | divStyle = { 50 | width: '100%', 51 | height: '120px', 52 | backgroundPosition: 'center', 53 | backgroundSize: 'cover', 54 | backgroundRepeat: 'no-repeat', 55 | backgroundImage: `url(${data.cover})`, 56 | }; 57 | 58 | return ( 59 | 60 | {data && 61 | 62 |
63 |
64 |
65 |
66 |
67 | 68 |
69 |
70 | 71 | {data && data.photo? 72 | 73 | : 74 |

75 | {data.username.charAt(0)} 76 |

77 | } 78 | 79 |
80 |
81 |
82 |
83 |
84 |

{data.fullname}

85 |
@{data.username}
86 |
87 |
88 | 89 | Following 90 | 91 | | 92 | 93 | Followers 94 | 95 |
96 |
97 |

{data.about}

98 |
99 | {currentUser && currentUser.username!=data.username && 100 |
101 | 102 |
103 | } 104 |
105 |
106 |
107 |
108 | } 109 |
110 | ); 111 | } 112 | } 113 | 114 | export default Card; -------------------------------------------------------------------------------- /dione/src/components/CardLists.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from "react"; 2 | import { Link } from 'react-router-dom'; 3 | import key from "weak-key"; 4 | import UserService from './UserService'; 5 | 6 | const userService = new UserService(); 7 | 8 | class CardLists extends PureComponent { 9 | constructor(props) { 10 | super(props); 11 | 12 | this.state = { 13 | data: null, loaded: false 14 | }; 15 | } 16 | 17 | componentDidMount(){ 18 | const { username } = this.props; 19 | 20 | if(username){ 21 | userService.getUserList(username).then(res => { 22 | console.log('load getUserList'); 23 | console.log(res); 24 | 25 | const data = res.data; 26 | this.setState({ data: data, loaded: true }); 27 | 28 | }).catch(()=>{ 29 | console.log('There was an error!'); 30 | this.setState({ loaded: true }); 31 | }); 32 | } 33 | } 34 | 35 | render() { 36 | const { data, loaded } = this.state; 37 | const { username } = this.props; 38 | 39 | return ( 40 | 41 |
42 | Lists 43 | {data && data.map(hit => 44 | {`/`+hit.list} 45 | ) 46 | } 47 |
48 |
49 | ); 50 | } 51 | } 52 | 53 | export default CardLists; 54 | -------------------------------------------------------------------------------- /dione/src/components/CardTopUsers.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from "react"; 2 | import { Link } from 'react-router-dom'; 3 | import key from "weak-key"; 4 | import UserService from './UserService'; 5 | 6 | const userService = new UserService(); 7 | 8 | class CardTopUsers extends PureComponent { 9 | constructor(props) { 10 | super(props); 11 | 12 | this.state = { 13 | data: null, loaded: false 14 | }; 15 | } 16 | 17 | componentDidMount(){ 18 | 19 | userService.getTopUser().then(res => { 20 | console.log('load getTopUser',res); 21 | 22 | const data = res.data; 23 | this.setState({ data: data, loaded: true }); 24 | 25 | }).catch(()=>{ 26 | console.log('There was an error!'); 27 | this.setState({ loaded: true }); 28 | }); 29 | } 30 | 31 | render() { 32 | const { data, loaded } = this.state; 33 | 34 | return ( 35 | 36 |
37 |

See who’s here

38 | 39 |
40 | {data && data.map(hit => 41 |
42 | 43 | {hit.photo? 44 |
45 | 46 |
47 | : 48 |

49 | {hit.username} 50 |

51 | } 52 | 53 |
54 | ) 55 | } 56 |
57 |
58 |
59 | ); 60 | } 61 | } 62 | 63 | export default CardTopUsers; -------------------------------------------------------------------------------- /dione/src/components/Context.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export const AuthContext = React.createContext(); -------------------------------------------------------------------------------- /dione/src/components/Emoji.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export default function Emoji(props) { 4 | 5 | return ( 6 | 7 | {props.value && 8 | 9 | } 10 | 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /dione/src/components/EmojiFilter.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Link } from 'react-router-dom'; 3 | 4 | export default function EmojiFilter ({ username }) { 5 | return ( 6 | 7 |

8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 |

49 |
50 | ); 51 | } -------------------------------------------------------------------------------- /dione/src/components/FollowButton.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from "react"; 2 | import { Link } from 'react-router-dom'; 3 | import throttle from 'lodash.throttle'; 4 | import UserService from './UserService'; 5 | 6 | const userService = new UserService(); 7 | const waitTime = 1000; 8 | 9 | class FollowButton extends PureComponent { 10 | constructor(props) { 11 | super(props); 12 | 13 | this.state = { status: "", disabled: false}; 14 | 15 | this.SendFollow = throttle(this.SendFollow.bind(this), waitTime); // debouncing function to 200ms and binding this 16 | this.SendUnFollow = throttle(this.SendUnFollow.bind(this), waitTime); // debouncing function to 200ms and binding this 17 | } 18 | 19 | componentDidMount(){ 20 | const { username } = this.props; 21 | console.log('FollowButton username:'+username); 22 | 23 | if(username){ 24 | userService.getFriendship(username).then(res => { 25 | console.log('load getFriendship'); 26 | console.log(res); 27 | //console.log(res.data[0].status); 28 | if(res.status=='200'){ 29 | //const data = res; 30 | if(res && res.data.length>0) 31 | this.setState({ status: res.data[0].status }); 32 | else 33 | this.setState({ status: "nofollow" }); 34 | } 35 | 36 | }).catch(()=>{ 37 | console.log('There was an error!'); 38 | }); 39 | } 40 | } 41 | 42 | handleClickFollow = () => { 43 | if(!this.state.disabled){ 44 | this.setState({ disabled: true }); 45 | console.log("seguir"); 46 | 47 | this.SendFollow(); 48 | } 49 | } 50 | 51 | handleClickUnFollow = () => { 52 | if(!this.state.disabled){ 53 | this.setState({ disabled: true }); 54 | console.log("des-seguir"); 55 | 56 | this.SendUnFollow(); 57 | } 58 | } 59 | 60 | SendFollow = () => { 61 | const {username} = this.props; 62 | 63 | userService.createFriendship(username, 64 | { 65 | "status": "following", 66 | } 67 | ).then((res)=>{ 68 | console.log("Friendship created!"); 69 | console.log(res); 70 | 71 | if(res.status=='201') 72 | this.setState({ status: res.data.status, disabled: false }); 73 | else 74 | this.setState({ status: "nofollow", disabled: false }); 75 | 76 | }).catch(()=>{ 77 | console.log('There was an error!.'); 78 | this.setState({ status: "nofollow", disabled: false }); 79 | }); 80 | } 81 | 82 | SendUnFollow = () => { 83 | const { username } = this.props; 84 | 85 | userService.destroyFriendship(username).then((res)=>{ 86 | console.log("Friendship deleted!"); 87 | console.log(res); 88 | 89 | if(res.status=='204') 90 | this.setState({ status: "nofollow", disabled: false }); 91 | else 92 | this.setState({ status: "following", disabled: false }); 93 | 94 | }).catch(()=>{ 95 | console.log('There was an error!.'); 96 | this.setState({ status: "following", disabled: false }); 97 | }); 98 | } 99 | 100 | render() { 101 | const {status,disabled,photo} = this.state; 102 | const {username} = this.props; 103 | 104 | return ( 105 | 106 | { status && status=='nofollow' && 107 | 108 | Follow to @{username} 109 | 110 | } 111 | { status && status=='following' && 112 |
113 |
114 | 117 |
118 | 125 |
126 | } 127 |
128 | ); 129 | } 130 | } 131 | 132 | 133 | export default FollowButton; 134 | -------------------------------------------------------------------------------- /dione/src/components/HomePage.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from "react"; 2 | 3 | import HomePosts from './HomePosts'; 4 | import CardTopUsers from './CardTopUsers'; 5 | 6 | class HomePage extends PureComponent { 7 | constructor(props) { 8 | super(props); 9 | } 10 | 11 | render() { 12 | return ( 13 | 14 |
15 |
16 |

Save your links, it's free and social.

17 |

— Pegao helps you organize your links in lists or simply with emojis.

18 |

19 | 20 | 21 | 22 | 23 | Sign Up Free 24 | 25 |

26 |

27 | By using Pegao, you agree to our Privacy policy and Terms of service. 28 |

29 |
30 | 31 |
32 | 33 |
34 | 35 |
36 | 37 |
38 | 39 |
40 |

Log in to start

41 |

42 | 43 | 44 | 45 | 46 | Sign Up Free 47 | 48 |

49 |

50 | By using Pegao, you agree to our Privacy policy and Terms of service. 51 |

52 |
53 | 54 |
55 |
56 | ); 57 | } 58 | } 59 | 60 | export default HomePage; 61 | -------------------------------------------------------------------------------- /dione/src/components/HomePosts.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from "react"; 2 | import key from "weak-key"; 3 | import throttle from 'lodash.throttle'; 4 | import PostService from './PostService'; 5 | import Post from './Postv2'; 6 | 7 | const postService = new PostService(); 8 | const waitTime = 1000; 9 | 10 | class HomePosts extends PureComponent { 11 | constructor(props) { 12 | super(props); 13 | 14 | this.state = { 15 | data: null, 16 | loaded: false, 17 | next_page: 1, 18 | disabled: false, 19 | }; 20 | 21 | this.fetchPosts = throttle(this.fetchPosts.bind(this), waitTime); // debouncing function to 200ms and binding this 22 | } 23 | 24 | componentDidMount(){ 25 | this.fetchPosts(); 26 | } 27 | 28 | handleClickMore = () => { 29 | if(!this.state.disabled){ 30 | this.setState({ loaded: false, disabled: true }); 31 | 32 | this.fetchPosts(); 33 | } 34 | } 35 | 36 | fetchPosts = () => { 37 | postService.getRecentPosts(this.state.next_page).then(res => { 38 | console.log('load getPostsbyUser',this.state.next_page); 39 | console.log(res); 40 | 41 | const data = res.data.results; 42 | const next_page = res.data.next; 43 | 44 | if(!this.state.data){ 45 | this.setState({ data: data, next_page: next_page, loaded: true, disabled: false }); 46 | }else{ 47 | this.setState({ data: [...this.state.data,...data,], next_page: next_page, loaded: true, disabled: false }); 48 | } 49 | 50 | }).catch(()=>{ 51 | console.log('There was an error!'); 52 | this.setState({ loaded: true, disabled: false }); 53 | }); 54 | } 55 | 56 | render() { 57 | const { data, loaded, disabled } = this.state; 58 | 59 | let message; 60 | 61 | return ( 62 | 63 |
64 |

Recent Links

65 |
66 | {data && data.map(hit => 67 | 68 | )} 69 |
70 | {(loaded && this.state.next_page) 71 | ? 72 | 73 | 74 | 75 | Load more 76 | 77 | : "" 78 | } 79 |
80 |
81 | ); 82 | } 83 | } 84 | 85 | export default HomePosts; -------------------------------------------------------------------------------- /dione/src/components/List.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from "react"; 2 | import key from "weak-key"; 3 | import throttle from 'lodash.throttle'; 4 | import PostService from './PostService'; 5 | import Post from './Postv2'; 6 | 7 | const postService = new PostService(); 8 | const waitTime = 1000; 9 | 10 | class List extends PureComponent { 11 | constructor(props) { 12 | super(props); 13 | 14 | this.state = { 15 | data: null, 16 | loaded: false, 17 | next_page: 1, 18 | disabled: false, 19 | }; 20 | 21 | this.fetchPosts = throttle(this.fetchPosts.bind(this), waitTime); // debouncing function to 200ms and binding this 22 | } 23 | 24 | componentDidMount(){ 25 | this.fetchPosts(); 26 | } 27 | 28 | handleClickMore = () => { 29 | if(!this.state.disabled){ 30 | this.setState({ loaded: false, disabled: true }); 31 | 32 | this.fetchPosts(); 33 | } 34 | } 35 | 36 | fetchPosts = () => { 37 | const { match: { params } } = this.props; 38 | 39 | console.log('Posts params.username:'+params.username); 40 | console.log("Posts this.props.match",this.props.match) 41 | 42 | //console.time("timer fetchPosts"); 43 | 44 | if(params && params.username && params.list && this.state.next_page){ 45 | postService.getPostsbyUserList(params.username,params.list,this.state.next_page).then(res => { 46 | console.log('load List',this.state.next_page); 47 | console.log(res); 48 | 49 | const data = res.data.results; 50 | const next_page = res.data.next; 51 | 52 | //console.log("this.state.data original",this.state.data); 53 | 54 | if(!this.state.data){ 55 | this.setState({ data: data, next_page: next_page, loaded: true, disabled: false }); 56 | }else{ 57 | this.setState({ data: [...this.state.data,...data,], next_page: next_page, loaded: true, disabled: false }); 58 | } 59 | //console.log("this.state.data joined",this.state.data); 60 | 61 | }).catch(()=>{ 62 | console.log('There was an error!'); 63 | this.setState({ loaded: true, disabled: false }); 64 | }); 65 | } 66 | 67 | //console.timeEnd("timer fetchPosts"); 68 | } 69 | 70 | render() { 71 | const { data, loaded, disabled } = this.state; 72 | const { match: { params, path } } = this.props; 73 | let message; 74 | 75 | if(loaded && data){ 76 | if(data.length===0){ //si no devuelve datos, data se crea pero data.length no se crea. 77 | if(params && params.username) 78 | message =
Sorry, this page is empty!
; 79 | } 80 | } 81 | 82 | return ( 83 | 84 |
85 | All post by the list {params.list} 86 | {message} 87 |
88 | {data && data.map(hit => 89 | 90 | )} 91 |
92 | {(loaded && this.state.next_page) 93 | ? 94 | 95 | 96 | 97 | Load more 98 | 99 | : "" 100 | } 101 |
102 |
103 | ); 104 | } 105 | } 106 | 107 | export default List; -------------------------------------------------------------------------------- /dione/src/components/Menu.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from "react"; 2 | import { Link } from 'react-router-dom'; 3 | 4 | import { AuthContext } from './Context'; 5 | 6 | class Menu extends PureComponent { 7 | static contextType = AuthContext; 8 | 9 | constructor(props) { 10 | super(props); 11 | this.state = { isActive: false, } 12 | } 13 | 14 | toggleNav = () => { 15 | console.log("this.state.isActive",this.state.isActive); 16 | 17 | this.setState(prevState => ({ 18 | isActive: !prevState.isActive 19 | })) 20 | } 21 | 22 | 23 | render() { 24 | let currentUser = this.context.currentUser; 25 | 26 | return ( 27 | 28 | {this.context.currentUser && 29 | 148 | } 149 | {!this.context.currentUser && 150 | 189 | } 190 | 191 | ); 192 | } 193 | } 194 | 195 | 196 | export default Menu; 197 | -------------------------------------------------------------------------------- /dione/src/components/MenuProfile.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Link } from 'react-router-dom'; 3 | 4 | export default function MenuProfile ({ username, tab }) { 5 | return ( 6 |
7 | 8 | 9 | All Posts 10 | 11 | 12 | 13 | 14 | 15 | Likes 16 | 17 | 18 |
19 | ) 20 | } -------------------------------------------------------------------------------- /dione/src/components/PostService.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | const API_URL = ''; 3 | 4 | axios.defaults.xsrfHeaderName = "X-CSRFTOKEN"; 5 | axios.defaults.xsrfCookieName = "csrftoken"; 6 | 7 | export default class PostService { 8 | 9 | constructor(){} 10 | 11 | getSubmit(purl) { 12 | const url = `${API_URL}/api/posts/submit?url=${purl}`; 13 | return axios.get(url).then(response => response); 14 | } 15 | 16 | getPosts(page) { 17 | //console.log("getPosts"); 18 | const url = `${API_URL}/api/posts/?page=${page}`; 19 | return axios.get(url).then(response => response); 20 | } 21 | 22 | getRecentPosts(page) { 23 | //console.log("getPosts"); 24 | const url = `${API_URL}/api/posts/recent?page=${page}`; 25 | return axios.get(url).then(response => response); 26 | } 27 | 28 | getPostsbyUser(username,page) { 29 | //console.log("getPosts"); 30 | const url = `${API_URL}/api/posts/${username}?page=${page}`; 31 | return axios.get(url).then(response => response); 32 | } 33 | 34 | getPostsbyUserList(username,list,page) { 35 | //console.log("getPosts"); 36 | const url = `${API_URL}/api/posts/${username}/lists/${list}?page=${page}`; 37 | return axios.get(url).then(response => response); 38 | } 39 | 40 | getPostsbyUserEmoji(username,emoji,page) { 41 | //console.log("getPosts"); 42 | const url = `${API_URL}/api/posts/${username}/emojis/${emoji}?page=${page}`; 43 | return axios.get(url).then(response => response); 44 | } 45 | 46 | updatePost(post){ 47 | console.log('post:'+JSON.stringify(post)); 48 | const url = `${API_URL}/api/posts/update`; 49 | return axios.post(url,post); 50 | } 51 | 52 | updateView(post){ 53 | const url = `${API_URL}/api/activities/${post.id}/view`; 54 | return axios.post(url,post); 55 | } 56 | 57 | updateVote(post){ 58 | const url = `${API_URL}/api/activities/${post.id}/vote`; 59 | return axios.post(url,post); 60 | } 61 | 62 | destroyVote(id){ 63 | const url = `${API_URL}/api/activities/${id}/vote/destroy`; 64 | return axios.delete(url); 65 | } 66 | 67 | createRePost(post){ 68 | const url = `${API_URL}/api/activities/${post.id}/repost`; 69 | return axios.post(url,post); 70 | } 71 | 72 | destroyRePost(id){ 73 | const url = `${API_URL}/api/activities/${id}/repost/destroy`; 74 | return axios.delete(url); 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /dione/src/components/Posts.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from "react"; 2 | import key from "weak-key"; 3 | import throttle from 'lodash.throttle'; 4 | import PostService from './PostService'; 5 | import Post from './Postv2'; 6 | 7 | const postService = new PostService(); 8 | const waitTime = 1000; 9 | 10 | class Posts extends PureComponent { 11 | constructor(props) { 12 | super(props); 13 | 14 | this.state = { 15 | data: null, 16 | loaded: false, 17 | next_page: 1, 18 | disabled: false, 19 | }; 20 | 21 | this.fetchPosts = throttle(this.fetchPosts.bind(this), waitTime); // debouncing function to 200ms and binding this 22 | } 23 | 24 | componentDidMount(){ 25 | this.fetchPosts(); 26 | } 27 | 28 | handleClickMore = () => { 29 | if(!this.state.disabled){ 30 | this.setState({ loaded: false, disabled: true }); 31 | 32 | this.fetchPosts(); 33 | } 34 | } 35 | 36 | fetchPosts = () => { 37 | const { match: { params } } = this.props; 38 | 39 | console.log('Posts params.username:'+params.username); 40 | console.log("Posts this.props.match",this.props.match) 41 | 42 | //console.time("timer fetchPosts"); 43 | 44 | if(params && params.username && this.state.next_page){ 45 | postService.getPostsbyUser(params.username,this.state.next_page).then(res => { 46 | console.log('load getPostsbyUser',this.state.next_page); 47 | console.log(res); 48 | 49 | const data = res.data.results; 50 | const next_page = res.data.next; 51 | 52 | //console.log("this.state.data original",this.state.data); 53 | 54 | if(!this.state.data){ 55 | this.setState({ data: data, next_page: next_page, loaded: true, disabled: false }); 56 | }else{ 57 | this.setState({ data: [...this.state.data,...data,], next_page: next_page, loaded: true, disabled: false }); 58 | } 59 | //console.log("this.state.data joined",this.state.data); 60 | 61 | }).catch(()=>{ 62 | console.log('There was an error!'); 63 | this.setState({ loaded: true, disabled: false, next_page: null }); 64 | }); 65 | } 66 | 67 | //console.timeEnd("timer fetchPosts"); 68 | } 69 | 70 | render() { 71 | const { data, loaded, disabled } = this.state; 72 | const { match: { params, path } } = this.props; 73 | let message; 74 | 75 | if(loaded && data){ 76 | if(data.length===0){ //si no devuelve datos, 'data' se crea pero data.length no se crea. 77 | if(params && params.username) 78 | message =
@{params.username} hasn’t pasted any link
; 79 | } 80 | } 81 | if(loaded && !data){ 82 | message =
This account does not exist
; 83 | } 84 | 85 | return ( 86 | 87 |
88 | All Posts 89 | {message} 90 |
91 | {data && data.map(hit => 92 | 93 | )} 94 |
95 | {(loaded && this.state.next_page) 96 | ? 97 | 98 | 99 | 100 | Load more 101 | 102 | : "" 103 | } 104 |
105 |
106 | ); 107 | } 108 | } 109 | 110 | export default Posts; -------------------------------------------------------------------------------- /dione/src/components/PostsEmoji.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from "react"; 2 | import key from "weak-key"; 3 | import throttle from 'lodash.throttle'; 4 | import Emoji from './Emoji'; 5 | import PostService from './PostService'; 6 | import Post from './Postv2'; 7 | 8 | const postService = new PostService(); 9 | const waitTime = 1000; 10 | 11 | class PostsEmoji extends PureComponent { 12 | constructor(props) { 13 | super(props); 14 | 15 | this.state = { 16 | data: null, 17 | loaded: false, 18 | next_page: 1, 19 | disabled: false, 20 | }; 21 | 22 | this.fetchPosts = throttle(this.fetchPosts.bind(this), waitTime); // debouncing function to 200ms and binding this 23 | } 24 | 25 | componentDidMount(){ 26 | this.fetchPosts(); 27 | } 28 | 29 | handleClickMore = () => { 30 | if(!this.state.disabled){ 31 | this.setState({ loaded: false, disabled: true }); 32 | 33 | this.fetchPosts(); 34 | } 35 | } 36 | 37 | fetchPosts = () => { 38 | const { match: { params } } = this.props; 39 | 40 | console.log('PostsEmoji params.username:'+params.username); 41 | console.log("PostsEmoji this.props.match",this.props.match) 42 | console.log("PostsEmoji emoji",params.emoji) 43 | 44 | //console.time("timer fetchPosts"); 45 | 46 | if(params && params.username && params.emoji && this.state.next_page){ 47 | postService.getPostsbyUserEmoji(params.username,params.emoji,this.state.next_page).then(res => { 48 | console.log('load List',this.state.next_page); 49 | console.log(res); 50 | 51 | const data = res.data.results; 52 | const next_page = res.data.next; 53 | 54 | //console.log("this.state.data original",this.state.data); 55 | 56 | if(!this.state.data){ 57 | this.setState({ data: data, next_page: next_page, loaded: true, disabled: false }); 58 | }else{ 59 | this.setState({ data: [...this.state.data,...data,], next_page: next_page, loaded: true, disabled: false }); 60 | } 61 | //console.log("this.state.data joined",this.state.data); 62 | 63 | }).catch(()=>{ 64 | console.log('There was an error!'); 65 | this.setState({ loaded: true, disabled: false }); 66 | }); 67 | } 68 | 69 | //console.timeEnd("timer fetchPosts"); 70 | } 71 | 72 | render() { 73 | const { data, loaded, disabled } = this.state; 74 | const { match: { params, path } } = this.props; 75 | let message; 76 | 77 | if(loaded && data){ 78 | if(data.length===0){ //si no devuelve datos, data se crea pero data.length no se crea. 79 | if(params && params.username) 80 | message =
Sorry, this page is empty!
; 81 | } 82 | } 83 | 84 | return ( 85 | 86 |
87 | All posts by 88 |
89 | {message} 90 | {data && data.map(hit => 91 | 92 | )} 93 |
94 | {(loaded && this.state.next_page) 95 | ? 96 | 97 | 98 | 99 | Load more 100 | 101 | : "" 102 | } 103 |
104 |
105 | ); 106 | } 107 | } 108 | 109 | export default PostsEmoji; -------------------------------------------------------------------------------- /dione/src/components/Postv2.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from "react"; 2 | import { Link } from 'react-router-dom'; 3 | import Emoji from './Emoji'; 4 | 5 | class Postv2 extends PureComponent { 6 | constructor(props) { 7 | super(props); 8 | } 9 | 10 | render() { 11 | const { data, showphoto } = this.props; 12 | 13 | //const twitterUrl = `http://twitter.com/share?text=${data.text}&url=${data.link}`;//&hashtags=hashtag1,hashtag2,hashtag3`; 14 | //const facebookUrl = `https://www.facebook.com/sharer/sharer.php?u=${data.link}`; 15 | 16 | //console.time("timer"); 17 | let text; 18 | 19 | let styletext = "has-margin-top-5 has-line-height-15 has-text-dark is-size-5-1"; 20 | 21 | if(data.list){ 22 | let [before,after] = data.text.split(' /'+data.list); 23 | 24 | text = data.list && before? 25 | 26 | {before} {`/`+data.list} {after} 27 | 28 | : 29 | {data.text}; 30 | ; 31 | }else{ 32 | text = {data.text}; 33 | } 34 | //console.timeEnd("timer"); 35 | 36 | let style = "columns posts is-gapless is-mobile has-margin-bottom-0 is-multiline has-padding-10 has-padding-bottom-15"; 37 | 38 | // 39 | //{format(data.created_at, 'D MMM H:mm')} 40 | 41 | let dirtyDate = new Date(data.created_at); 42 | let date = dirtyDate.toDateString();//.getDate(); //.toString() 43 | 44 | return ( 45 | 46 |
47 |
48 | {showphoto && 49 |
50 | 51 | {data.photo? 52 |
53 | 54 |
55 | : 56 |

57 | {data.username.charAt(0)} 58 |

59 | } 60 | 61 |
62 | 63 |
64 |
65 | } 66 |
67 |
68 | 69 | 70 | {data.username} 71 | 72 | 73 | {text} 74 | {date} 75 |
76 |
77 |
78 |
79 |
80 |
81 | ); 82 | } 83 | } 84 | 85 | export default Postv2; -------------------------------------------------------------------------------- /dione/src/components/RePost.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from "react"; 2 | import throttle from 'lodash.throttle'; 3 | import PostService from './PostService'; 4 | 5 | const postService = new PostService(); 6 | const waitTime = 1000; 7 | 8 | class RePost extends PureComponent { 9 | constructor(props) { 10 | super(props); 11 | 12 | this.state = { repost: 0, reposted: this.props.reposted, disabled: false }; 13 | 14 | this.SendRePost = throttle(this.SendRePost.bind(this), waitTime); // debouncing function to 200ms and binding this 15 | this.SendUnRePost = throttle(this.SendUnRePost.bind(this), waitTime); // debouncing function to 200ms and binding this 16 | } 17 | 18 | componentDidMount(){ 19 | this.setState({ repost: this.props.repost }) 20 | } 21 | 22 | handleClickRepost = () => { 23 | if(!this.state.disabled){ 24 | this.setState({ disabled: true }); 25 | console.log("votando"); 26 | 27 | this.SendRePost(); 28 | } 29 | } 30 | 31 | handleClickUnRepost = () => { 32 | if(!this.state.disabled){ 33 | this.setState({ disabled: true }); 34 | console.log("des-votando"); 35 | 36 | this.SendUnRePost(); 37 | } 38 | } 39 | 40 | SendRePost = () => { 41 | postService.createRePost( 42 | { 43 | "post": this.props.id, 44 | "status": 'repost', 45 | } 46 | ).then((res)=>{ 47 | //console.log(res); 48 | console.log("reposted!"); 49 | 50 | if(res.status=='201') 51 | this.setState({ repost : this.state.repost + 1, reposted: true, disabled: false }); 52 | else 53 | this.setState({ reposted: false, disabled: false }); 54 | 55 | }).catch(()=>{ 56 | console.log('There was an error! Please re-check your form.'); 57 | this.setState({ reposted: false, disabled: false }); 58 | }); 59 | } 60 | 61 | SendUnRePost = () => { 62 | postService.destroyRePost(this.props.id).then((res)=>{ 63 | //console.log(res); 64 | console.log("unreposted!"); 65 | 66 | if(res.status=='204') 67 | this.setState({ repost : this.state.repost - 1, reposted: false, disabled: false }); 68 | else 69 | this.setState({ reposted: true, disabled: false }); 70 | 71 | }).catch(()=>{ 72 | console.log('There was an error! Please re-check your form.'); 73 | this.setState({ reposted: true, disabled: false }); 74 | }); 75 | } 76 | 77 | render() { 78 | const {repost,reposted,disabled} = this.state; 79 | 80 | return ( 81 | 82 |
83 | 102 |
103 |
104 | ); 105 | 106 | 107 | } 108 | } 109 | 110 | export default RePost; 111 | -------------------------------------------------------------------------------- /dione/src/components/RouteHandler.js: -------------------------------------------------------------------------------- 1 | import React, { useContext } from "react"; 2 | import { Route } from "react-router-dom"; 3 | 4 | import { AuthContext } from './Context'; 5 | 6 | const RouteHandler = ({ RouteComponent, FallbackComponent, ...rest }) => { 7 | const {currentUser} = useContext(AuthContext); 8 | 9 | return ( 10 | 13 | !!currentUser ? ( 14 | 15 | ) : ( 16 | 17 | ) 18 | } 19 | /> 20 | ); 21 | }; 22 | 23 | export default RouteHandler; -------------------------------------------------------------------------------- /dione/src/components/Routes.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from "react"; 2 | import { Route, BrowserRouter, Switch, Redirect } from 'react-router-dom'; 3 | 4 | import withAuth from "./withAuth"; 5 | import Menu from './Menu'; 6 | import RouteHandler from './RouteHandler'; 7 | import HomePage from './HomePage'; 8 | import UserFeed from './UserFeed'; 9 | import UserProfile from './UserProfile'; 10 | import Submit from './Submit'; 11 | 12 | class Routes extends PureComponent { 13 | render() { 14 | return ( 15 | 16 | 17 | 18 | 19 | } /> 20 | } /> 21 | 22 |
23 |
24 | 25 | ()} FallbackComponent={HomePage} /> 26 | ()} /> 27 | ()} /> 28 | 29 | } /> 30 | 31 |
32 |
33 | 34 | 35 | 36 | ); 37 | 38 | 39 | } 40 | } 41 | 42 | export default withAuth(Routes); -------------------------------------------------------------------------------- /dione/src/components/SearchText.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from "react"; 2 | 3 | class SearchText extends PureComponent { 4 | constructor(props) { 5 | super(props); 6 | } 7 | 8 | handleSubmit = e => { 9 | this.props.onFilterTextChange(e.target.search.value); 10 | e.preventDefault(); 11 | }; 12 | 13 | render() { 14 | return ( 15 |
16 |
17 |
18 |

19 | 20 |

21 |

22 | 27 |

28 |
29 |
30 |
31 | ); 32 | } 33 | } 34 | 35 | export default SearchText; -------------------------------------------------------------------------------- /dione/src/components/Submit.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from "react"; 2 | import throttle from 'lodash.throttle'; 3 | import PostService from './PostService'; 4 | 5 | const postService = new PostService(); 6 | const waitTime = 1000; 7 | 8 | class Submit extends PureComponent { 9 | constructor(props) { 10 | super(props); 11 | 12 | this.state = { 13 | formFields: { 14 | link: '', 15 | emoji: '', 16 | text: '', 17 | }, 18 | emojis: [ 19 | { 20 | emoji:'speaking_head', 21 | label: 'Share', 22 | }, 23 | { 24 | emoji:'heart', 25 | label: 'Love', 26 | }, 27 | /*{ 28 | emoji:'thumbsdown', 29 | label: 'Dislike', 30 | },*/ 31 | { 32 | emoji:'bulb', 33 | label: 'Idea', 34 | }, 35 | { 36 | emoji:'star', 37 | label: 'Fav page', 38 | }, 39 | { 40 | emoji:'round_pushpin', 41 | label: 'Place', 42 | }, 43 | { 44 | emoji:'shopping_trolley', 45 | label: 'Shopping', 46 | }, 47 | { 48 | emoji:'gift', 49 | label: 'Gift list', 50 | }, 51 | { 52 | emoji:'bookmark', 53 | label: 'Read later', 54 | }, 55 | ], 56 | currentcolumn: -1, 57 | disabled: false, 58 | sent: false, 59 | message: '', 60 | }; 61 | 62 | this.SendPost = throttle(this.SendPost.bind(this), waitTime); // debouncing function to 200ms and binding this 63 | } 64 | 65 | handleClickEmoji = column => { 66 | const {formFields,currentcolumn} = this.state; 67 | const emojis = this.state.emojis.slice(); 68 | 69 | console.log('column',column); 70 | console.log('currentcolumn',currentcolumn); 71 | console.log('emojis[column]',emojis[column]); 72 | 73 | if(column != currentcolumn){ 74 | formFields['emoji'] = emojis[column].emoji; 75 | console.log("formFields['emoji']",formFields['emoji']); 76 | 77 | return this.setState({ currentcolumn: column, formFields: {...formFields } }); 78 | } 79 | 80 | //const emojilists = [ 81 | /*{ 82 | id: 0, 83 | list: [ 84 | { 85 | emoji:'speaking_head', 86 | label: 'Share', 87 | }, 88 | { 89 | emoji:'computer', 90 | label: 'Tech', 91 | }, 92 | { 93 | emoji:'family', 94 | label: 'Family', 95 | }, 96 | { 97 | emoji:'woman_lifting_weights', 98 | label: 'Fit', 99 | }, 100 | ] 101 | },*/ 102 | /*{ 103 | id: 1, 104 | list: [ 105 | { 106 | emoji:'smiley', 107 | label: 'Like', 108 | }, 109 | { 110 | emoji:'heart', 111 | label: 'Love', 112 | }, 113 | ] 114 | },*/ 115 | /*{ 116 | id: 2, 117 | list: [ 118 | { 119 | emoji:'rage', 120 | label: 'Angru', 121 | }, 122 | { 123 | emoji:'hushed', 124 | label: 'Wow', 125 | }, 126 | { 127 | emoji:'cry', 128 | label: 'Sad', 129 | },*/ 130 | //] 131 | //}, 132 | //]; 133 | 134 | //se ubica en la lista correcta 135 | /*let emojilist = emojilists.find(item => { 136 | return item.id === column; 137 | }); 138 | console.log('emojilist',emojilist)*/ 139 | 140 | //busca el siguiente emoji de la columna 141 | if(emojilist){ 142 | let index = emojilist.list.findIndex(item => { 143 | return item.emoji === emojis[column].emoji; 144 | }) + 1; 145 | console.log("index",index); 146 | 147 | if(index == emojilist.list.length) 148 | index = 0; 149 | 150 | console.log('next:',emojilist.list[index].emoji); 151 | 152 | emojis[column] = emojilist.list[index]; 153 | 154 | formFields['emoji'] = emojilist.list[index].emoji; 155 | console.log("formFields['emoji']",formFields['emoji']); 156 | 157 | this.setState({ emojis, currentcolumn: column, formFields: {...formFields } }); 158 | } 159 | } 160 | 161 | componentDidMount(){ 162 | const { match: { params } } = this.props; 163 | const {formFields} = this.state; 164 | 165 | console.log("submit",this.props.location); 166 | 167 | //suma a la url el valor en location.search, en caso que el link tengo query string ej. /?q=something 168 | postService.getSubmit(this.props.location.pathname.substr(1, this.props.location.pathname.length)+this.props.location.search).then(res => { 169 | console.log('load getSubmit'); 170 | console.log(res); 171 | 172 | if(res.data.url.length>0){ 173 | formFields['text'] = res.data.title; 174 | formFields['link'] = res.data.url; 175 | 176 | this.setState({ formFields: {...formFields } }); 177 | }else{ 178 | this.setState({disabled: true, message:'There was an error loading the page! Try again.'}); 179 | } 180 | 181 | }).catch(()=>{ 182 | console.log('There was an error!'); 183 | this.setState({disabled: true, message:'There was an error loading the page! Try again.'}); 184 | }); 185 | 186 | } 187 | 188 | handleSubmit = e => { 189 | e.preventDefault(); 190 | 191 | if(!this.state.disabled){ 192 | const {formFields} = this.state; 193 | 194 | if(!formFields['text'] || formFields['text'].length<=0){ 195 | this.setState({message: 'Write a text before pasting!'}); 196 | }else if(formFields['text'].length>150){ 197 | this.setState({message: 'Write a shorter text before pasting: '+(150-formFields['text'].length) }); 198 | }else if(!formFields['emoji'] || formFields['emoji'].length<=0){ 199 | this.setState({message: 'Choose an emoji before pasting!'}); 200 | }else{ 201 | this.setState({ disabled: true }); 202 | 203 | this.SendPost(); 204 | } 205 | } 206 | } 207 | 208 | SendPost(){ 209 | const {formFields} = this.state; 210 | 211 | postService.updatePost( 212 | formFields 213 | ).then((result)=>{ 214 | console.log("Post created!"); 215 | 216 | if(result.status=='201'){ 217 | this.setState({ sent: true, message: '' }); 218 | 219 | this.props.history.push("/"); 220 | }else 221 | this.setState({message: 'There was an error! Try again.', disabled: false}); 222 | 223 | }).catch(()=>{ 224 | console.log('There was an error!'); 225 | this.setState({message: 'There was an error! Try again.', disabled: false}); 226 | }); 227 | } 228 | 229 | handleChange = e => { 230 | const {formFields} = this.state; 231 | 232 | formFields['text'] = e.target.value; 233 | 234 | if(formFields['text'].length>120) 235 | this.setState({message: (150-formFields['text'].length) }); 236 | 237 | this.setState({ formFields: {...formFields } }); 238 | }; 239 | 240 | handleKeyDown = event => { 241 | const keyCode = event.keyCode || event.which 242 | 243 | //this.setState({message: keyCode}); 244 | 245 | if (keyCode === 13) { 246 | event.returnValue = false 247 | if (event.preventDefault) event.preventDefault() 248 | } 249 | } 250 | 251 | render() { 252 | const {formFields,disabled,message,sent,emojis} = this.state; 253 | 254 | const emojilist = emojis.map((emojis,index) => 255 | 256 | this.handleClickEmoji(index)}> 257 | 258 | 259 | 260 | ); 261 | 262 | return ( 263 | 264 |
265 |
266 |
267 |
268 |
269 |
270 |

Save as...

271 |
272 |
273 | 32 | 33 |
34 | 42 |

43 |

44 |
45 |
46 | {% endblock %} 47 | -------------------------------------------------------------------------------- /dione/templates/articles/tour.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block title %}Pegao Tour{% endblock %} 4 | 5 | {% block content %} 6 |
7 |
8 |
9 |
10 |

Start to paste the pages that inspire you and share them with your followers.

11 |

Just write in the address bar of the browser "pegao.co/" before the link you want to remember and press "Paste".

12 |
13 |
14 |
15 | 16 |
17 |
18 | 19 |
20 |

Organize your pages with emojis!

21 |
22 |
23 | 24 |
25 |
26 |

27 | — Say something. 28 |

29 |

30 | — Something you liked. 31 |

32 |

33 | — Capture your ideas. 34 |

35 |

36 | — Bookmark your favorite pages. 37 |

38 |

39 | — Remember a place. 40 |

41 |

42 | — Something to buy. 43 |

44 |

45 | — Your gift list. 46 |

47 |

48 | — Save for later. 49 |

50 |

51 | — And more... 52 |

53 |
54 |
55 |

Create your own lists

56 |

You can create your own lists — written with a / symbol — to easily categorize them.

57 |
58 |
59 | 60 |
61 |
62 |
63 |
64 |
65 | 66 |
67 |
68 |
69 |

The Home — Find out what people are saving and sharing right now.

70 |

Profile — This is your bookmark with your links.

71 |
72 |
73 |

Log in to start, it's easy

74 | 82 |
83 |
84 |
85 |
86 | {% endblock %} 87 | -------------------------------------------------------------------------------- /dione/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content %} 4 |
5 |
6 | 20 |
21 |
22 |

The easiest way to make lists of links on topics of your interest.

23 |
24 |
25 |

26 | 27 | 28 | 29 | 30 | Start now 31 | 32 |

33 |

34 | By using Pegao, you agree to our Privacy policy and Terms of service. 35 |

36 |
37 |
38 | 39 |
40 |
41 |
42 | 43 |
44 |
45 |
46 | 47 |

Remember, share and organize your links online — it's more fun

48 |

Save links directly from your browser, from any device, without any installation.

49 |

Create your own lists — written with a / symbol — or just choose an emoji.

50 |

Get your public profile — and share it with the world.

51 |
52 |
53 |
54 |
55 | 56 |
57 |
58 |

59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 |

68 |
69 |
70 |

What's next

71 |

72 | Ideas are always welcome, please tweet me 73 | 74 | @zakokor 75 | 76 |

77 |
78 |

Hi, it’s Gonzalo. I’m the solo developer of Pegao.co and it's currently running on a server that bills me $5 per month, but in the future I will need more than that. If you enjoy my work, please consider supporting what I do. Thank you.

79 |
80 |

81 | 82 | Become a Patron 83 | 84 | 85 | Buy Me a Coffee at ko-fi.com 86 | 87 | 88 | 89 | 90 | 91 | Share 92 | 93 | 94 | 95 | 96 | 97 | Tweet 98 | 99 |

100 |
101 |
102 |
103 |

Log in to start, it's easy

104 | 105 | 106 | 107 | 108 | Log in with Google 109 | 110 |
111 |
112 |
113 | 114 | {% endblock %} 115 | -------------------------------------------------------------------------------- /dione/templates/privacy.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block title %}Pegao - Privacy{% endblock %} 4 | 5 | {% block content %} 6 |
7 |
8 |

Privacy Policy

9 |

Effective date: July 3, 2019

10 |

This page informs you of our policies regarding the collection, use, and disclosure of personal data when you use our Service and the choices you have associated with that data.

11 |

We use your data to provide and improve the Service. By using the Service, you agree to the collection and use of information in accordance with this policy. Unless otherwise defined in this Privacy Policy, terms used in this Privacy Policy have the same meanings as in our Terms and Conditions, accessible from Pegao.co.

12 |

Definitions

13 |

“Service” is the Pegao.co website.

14 |

“Personal Data” means data about a living individual who can be identified from those data (or from those and other information either in our possession or likely to come into our possession).

15 |

“Usage Data” is data collected automatically either generated by the use of the Service or from the Service infrastructure itself (for example, the duration of a page visit).

16 |

"Cookies" are small pieces of data stored on your device (computer or mobile device).

17 |

"Data Controller" means the natural or legal person who (either alone or jointly or in common with other persons) determines the purposes for which and the manner in which any personal information are, or are to be, processed. For the purpose of this Privacy Policy, we are a Data Controller of your Personal Data.

18 |

"Data Processor" (or "Service Provider") means any natural or legal person who processes the data on behalf of the Data Controller. We may use the services of various Service Providers in order to process your data more effectively.

19 |

"Data Subject" is any living individual who is using our Service and is the subject of Personal Data.

20 |

Information Collection and Use

21 |

While using our Service, we may ask you to provide us with certain personally identifiable information that can be used to contact or identify you ("Personal Data"). Personally identifiable information may include, but is not limited to email addresses, first and last names, cookies, and usage data.

22 |

We may use your Personal Data to contact you with newsletters, marketing or promotional materials and other information that may be of interest to you. You may opt out of receiving any, or all, of these communications from us by following the unsubscribe link or instructions provided in any email we send.

23 |

We may also collect information how the Service is accessed and used ("Usage Data"). This Usage Data may include information such as your computer’s Internet Protocol address (e.g. IP address), browser type, browser version, the pages of our Service that you visit, the time and date of your visit, the time spent on those pages, unique device identifiers and other diagnostic data.

24 |

We use cookies and similar tracking technologies to track the activity on our Service and hold certain information.

25 |

Cookies are files with small amount of data which may include an anonymous unique identifier. Cookies are sent to your browser from a website and stored on your device. Tracking technologies also used are beacons, tags, and scripts to collect and track information and to improve and analyze our Service.

26 |

You can instruct your browser to refuse all cookies or to indicate when a cookie is being sent. However, if you do not accept cookies, you may not be able to use some portions of our Service.

27 |

We use Session Cookies to operate our Service. We use Preference Cookies to remember your preferences and various settings. We use Security Cookies for security purposes.

28 |

Use of Data

29 |

Pegao uses the collected data to provide and maintain our Service:

30 |
    31 |
  • To provide and maintain our Service
  • 32 |
  • To notify you about changes to our Service
  • 33 |
  • To allow you to participate in interactive features of our Service when you choose to do so
  • 34 |
  • To provide customer support
  • 35 |
  • To gather analysis or valuable information so that we can improve our Service
  • 36 |
  • To monitor the usage of our Service
  • 37 |
  • To detect, prevent and address technical issues
  • 38 |
39 |

Legal Basis for Processing Personal Data Under General Data Protection Regulation (GDPR)

40 |

If you are from the European Economic Area (EEA), Pegao legal basis for collecting and using the personal information described in this Privacy Policy depends on the Personal Data we collect and the specific context in which we collect it.

41 |

Pegao may process your Personal Data because:

42 |
    43 |
  • We need to perform a contract with you
  • 44 |
  • You have given us permission to do so
  • 45 |
  • The processing is in our legitimate interests and it’s not overridden by your rights
  • 46 |
  • For payment processing purposes
  • 47 |
  • To comply with the law
  • 48 |
49 |

Retention of Data

50 |

We will retain your Personal Data only for as long as is necessary for the purposes set out in this Privacy Policy. We will retain and use your Personal Data to the extent necessary to comply with our legal obligations, resolve disputes, and enforce our legal agreements and policies.

51 |

We will also retain Usage Data for internal analysis purposes. Usage Data is generally retained for a shorter period of time, except when this data is used to strengthen the security or to improve the functionality of our Service, or we are legally obligated to retain this data for longer time periods.

52 |

Transfer of Data

53 |

We store and process the information that we collect in the United States in accordance with this Privacy Policy. If you are located outside United States and choose to provide information to us, please note that we transfer the data, including Personal Data, to United States and process it there.

54 |

Your consent to this Privacy Policy followed by your submission of such information represents your agreement to that transfer.

55 |

We provide all of our users notice, choice, accountability, security, and access, and we limit the purpose for processing. We also provide our users a method of recourse and enforcement.

56 |

We will take all steps reasonably necessary to ensure that your data is treated securely and in accordance with this Privacy Policy and no transfer of your Personal Data will take place to an organization or a country unless there are adequate controls in place including the security of your data and other personal information.

57 |

Disclosure of Data

58 |

If we are involved in a merger, acquisition or asset sale, your Personal Data may be transferred. We will provide notice before your Personal Data is transferred and becomes subject to a different Privacy Policy.

59 |

Under certain circumstances, we may be required to disclose your Personal Data if required to do so by law or in response to valid requests by public authorities (e.g. a court or a government agency).

60 |

We may disclose your Personal Data in the good faith belief that such action is necessary to:

61 |
    62 |
  • To comply with a legal obligation
  • 63 |
  • To protect and defend the rights or property of Pegao
  • 64 |
  • To prevent or investigate possible wrongdoing in connection with the Service
  • 65 |
  • To protect the personal safety of users of the Service or the public
  • 66 |
  • To protect against legal liability
  • 67 |
68 |

Security of Data

69 |

The security of your data is important to us, but remember that no method of transmission over the Internet, or method of electronic storage is 100% secure. While we strive to use acceptable means to protect your Personal Data, we cannot guarantee its absolute security.

70 |

Your Data Protection Rights Under General Data Protection Regulation (GDPR)

71 |

If you are a resident of the European Economic Area (EEA), you have certain data protection rights. Pegao aims to take reasonable steps to allow you to correct, amend, delete, or limit the use of your Personal Data.

72 |

If you wish to be informed what Personal Data we hold about you and if you want it to be removed from our systems, please contact us.

73 |

In certain circumstances, you have the following data protection rights:

74 |

The right to access, update or to delete the information we have on you. Whenever made possible, you can access, update or request deletion of your Personal Data directly within your account settings section. If you are unable to perform these actions yourself, please contact us to assist you.

75 |

The right of rectification. You have the right to have your information rectified if that information is inaccurate or incomplete.

76 |

The right to object. You have the right to object to our processing of your Personal Data.

77 |

The right of restriction. You have the right to request that we restrict the processing of your personal information.

78 |

The right to data portability. You have the right to be provided with a copy of the information we have on you in a structured, machine-readable and commonly used format.

79 |

The right to withdraw consent. You also have the right to withdraw your consent at any time where we relied on your consent to process your personal information. Please note that we may ask you to verify your identity before responding to such requests.

80 |

You have the right to complain to a Data Protection Authority about our collection and use of your Personal Data. For more information, please contact your local data protection authority in the European Economic Area (EEA).

81 |

If you have concerns about the way Pegao is handling your Personal Data, please let us know immediately. We want to help. You may email us directly at gonza@pegao.co with the subject line “Privacy Concerns.” We will respond promptly — within 45 days at the latest.

82 |

Dispute resolution process

83 |

In the unlikely event that a dispute arises between you and Pegao regarding our handling of your Personal Data, we will do our best to resolve it.

84 |

Independent arbitration

85 |

Under certain limited circumstances, European Union individuals may invoke binding Privacy Shield arbitration as a last resort if all other forms of dispute resolution have been unsuccessful. To learn more about this method of resolution and its availability to you, please read more about Privacy Shield. Arbitration is not mandatory; it is a tool you can use if you choose to.

86 |

Service Providers

87 |

We may employ third party companies and individuals to facilitate our Service ("Service Providers"), to provide the Service on our behalf, to perform Service-related services or to assist us in analyzing how our Service is used.

88 |

These third parties have access to your Personal Data only to perform these tasks on our behalf and are obligated not to disclose or use it for any other purpose. We may use third-party Service Providers to monitor and analyze the use of our Service.

89 |

We use Google Analytics, a web analytics service offered by Google that tracks and reports website traffic. Google uses the data collected to track and monitor the use of our Service. This data is shared with other Google services. Google may use the collected data to contextualize and personalize the ads of its own advertising network.

90 |

You can opt-out of having made your activity on the Service available to Google Analytics by installing the Google Analytics opt-out browser add- on. The add-on prevents the Google Analytics JavaScript (ga.js, analytics.js, and dc.js) from sharing information with Google Analytics about visits activity.

91 |

We may provide paid products and/or services within the Service (e.g. payment processors).

92 |

We will not store or collect your payment card details. That information is provided directly to our third-party payment processors whose use of your personal information is governed by their Privacy Policy.

93 |

Links to Other Sites

94 |

Our Service may contain links to other sites that are not operated by us. If you click on a third party link, you will be directed to that third party’s site. We strongly advise you to review the Privacy Policy of every site you visit.

95 |

We have no control over and assume no responsibility for the content, privacy policies or practices of any third party sites or services.

96 |

Children’s Privacy

97 |

Our Service does not address anyone under the age of 13 (“Children”). If you’re a child under the age of 13, you may not have an account on Pegao.

98 |

We do not knowingly collect personally identifiable information from anyone under the age of 13. If you are a parent or guardian and you are aware that your Children has provided us with Personal Data, please contact us. If we become aware that we have collected Personal Data from children without verification of parental consent, we take steps to remove that information from our servers.

99 |

Changes to This Privacy Policy

100 |

We may update our Privacy Policy from time to time. We will notify you of any changes by posting the new Privacy Policy on this page.

101 |

For material changes to this Privacy Policy, we will notify you prior to the change taking effect.

102 |

You are advised to review this Privacy Policy periodically for any changes. Changes to this Privacy Policy are effective when they are posted on this page.

103 |

Contact Us

104 |

If you have any questions about this Privacy Policy, please contact us by email: gonza@pegao.co

105 |
106 |
107 | 120 | {% endblock %} 121 | -------------------------------------------------------------------------------- /dione/templates/terms.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block title %}Pegao - Terms of Service{% endblock %} 4 | 5 | {% block content %} 6 |
7 |
8 |

Terms of Service

9 |

Last updated July 3, 2019

10 |

These terms of service ("Terms") apply to your access and use of Pegao.co (the "Service"). Please read them carefully.

11 |

Accepting these Terms

12 |

If you access or use the Service, it means you agree to be bound by all of the terms below. So, before you use the Service, please read all of the terms. If you don’t agree to all of the terms below, please do not use the Service. Also, if a term does not make sense to you, please let us know by e-mailing gonza@pegao.co.

13 |

Changes to these Terms

14 |

We reserve the right to modify these Terms at any time. For instance, we may need to change these Terms if we come out with a new feature or for some other reason.

15 |

Whenever we make changes to these Terms, the changes are effective 2 days after we post such revised Terms (indicated by revising the date at the top of these Terms) or upon your acceptance if we provide a mechanism for your immediate acceptance of the revised Terms (such as a click-through confirmation or acceptance button). It is your responsibility to check Pegao for changes to these Terms.

16 |

If you continue to use the Service after the revised Terms go into effect, then you have accepted the changes to these Terms.

17 |

Privacy Policy

18 |

For information about how we collect and use information about users of the Service, please check out our privacy policy available at https://pegao.co/privacy.html.

19 |

Third-Party Services

20 |

From time to time, we may provide you with links to third party websites or services that we do not own or control. Your use of the Service may also include the use of applications that are developed or owned by a third party. Your use of such third party applications, websites, and services is governed by that party’s own terms of service or privacy policies. We encourage you to read the terms and conditions and privacy policy of any third party application, website or service that you visit or use.

21 |

Creating Accounts

22 |

When you create an account or use another service to log in to the Service, you agree to maintain the security of your password and accept all risks of unauthorized access to any data or other information you provide to the Service. If you discover or suspect any Service security breaches, please let us know as soon as possible.

23 |

Your Content & Conduct

24 |

Our Service allows you and other users to post, link and otherwise make available content. You are responsible for the content that you make available to the Service, including its legality, reliability, and appropriateness.

25 |

When you post, link or otherwise make available content to the Service, you grant us the right and license to use, reproduce, modify, publicly perform, publicly display and distribute your content on or through the Service. We may format your content for display throughout the Service, but we will not edit or revise the substance of your content itself.

26 |

Aside from our limited right to your content, you retain all of your rights to the content you post, link and otherwise make available on or through the Service.

27 |

You can remove the content that you posted by deleting it. Once you delete your content, it will not appear on the Service, but copies of your deleted content may remain in our system or backups for some period of time.

28 |

You may not post, link and otherwise make available on or through the Service any of the following:

29 |
    30 |
  • Content that is libelous, defamatory, bigoted, fraudulent or deceptive;
  • 31 |
  • Content that is illegal or unlawful, that would otherwise create liability;
  • 32 |
  • Content that may infringe or violate any patent, trademark, trade secret, copyright, right of privacy, right of publicity or other intellectual or other right of any party;
  • 33 |
  • Mass or repeated promotions, political campaigning or commercial messages directed at users who do not follow you (SPAM);
  • 34 |
  • Private information of any third party (e.g., addresses, phone numbers, email addresses, Social Security numbers and credit card numbers); and
  • 35 |
  • Viruses, corrupted data or other harmful, disruptive or destructive files or code.
  • 36 |
37 |

Also, you agree that you will not do any of the following in connection with the Service or other users:

38 |
    39 |
  • Use the Service in any manner that could interfere with, disrupt, negatively affect or inhibit other users from fully enjoying the Service or that could damage, disable, overburden or impair the functioning of the Service;
  • 40 |
  • Impersonate or post on behalf of any person or entity or otherwise misrepresent your affiliation with a person or entity;
  • 41 |
  • Collect any personal information about other users, or intimidate, threaten, stalk or otherwise harass other users of the Service;
  • 42 |
  • Create an account or post any content if you are not over 13 years of age years of age; and
  • 43 |
  • Circumvent or attempt to circumvent any filtering, security measures, rate limits or other features designed to protect the Service, users of the Service, or third parties.
  • 44 |
45 |

Hyperlinks and Third Party Content

46 |

You may create a hyperlink to the Service. But, you may not use, frame or utilize framing techniques to enclose any of our trademarks, logos or other proprietary information without our express written consent.

47 |

When you leave the Service, you should be aware that these Terms and our policies no longer govern.

48 |

If there is any content on the Service from you and others, we don’t review, verify or authenticate it, and it may include inaccuracies or false information. We make no representations, warranties, or guarantees relating to the quality, suitability, truth, accuracy or completeness of any content contained in the Service. You acknowledge sole responsibility for and assume all risk arising from your use of or reliance on any content.

49 |

Unavoidable Legal Stuff

50 |

THE SERVICE AND ANY OTHER SERVICE AND CONTENT INCLUDED ON OR OTHERWISE MADE AVAILABLE TO YOU THROUGH THE SERVICE ARE PROVIDED TO YOU ON AN AS IS OR AS AVAILABLE BASIS WITHOUT ANY REPRESENTATIONS OR WARRANTIES OF ANY KIND. WE DISCLAIM ANY AND ALL WARRANTIES AND REPRESENTATIONS (EXPRESS OR IMPLIED, ORAL OR WRITTEN) WITH RESPECT TO THE SERVICE AND CONTENT INCLUDED ON OR OTHERWISE MADE AVAILABLE TO YOU THROUGH THE SERVICE WHETHER ALLEGED TO ARISE BY OPERATION OF LAW, BY REASON OF CUSTOM OR USAGE IN THE TRADE, BY COURSE OF DEALING OR OTHERWISE.

51 |

You agree to defend, indemnify and hold us harmless from and against any and all costs, damages, liabilities, and expenses (including attorneys’ fees, costs, penalties, interest and disbursements) we incur in relation to, arising from, or for the purpose of avoiding, any claim or demand from a third party relating to your use of the Service or the use of the Service by any person using your account, including any claim that your use of the Service violates any applicable law or regulation, or the rights of any third party, and/or your violation of these Terms.

52 |

Copyright Complaints

53 |

We take intellectual property rights seriously. In accordance with the Digital Millennium Copyright Act ("DMCA") and other applicable law, we have adopted a policy of terminating, in appropriate circumstances and, at our sole discretion, access to the service for users who are deemed to be repeat infringers.

54 |

Governing Law

55 |

The validity of these Terms and the rights, obligations, and relations of the parties under these Terms will be construed and determined under and in accordance with the laws of the Colombia country, without regard to conflicts of law principles.

56 |

Jurisdiction

57 |

You expressly agree that exclusive jurisdiction for any dispute with the Service or relating to your use of it, resides in the courts of the Delaware state and you further agree and expressly consent to the exercise of personal jurisdiction in the courts of the Colombia country in connection with any such dispute including any claim involving Service. You further agree that you and Service will not commence against the other a class action, class arbitration or other representative action or proceeding.

58 |

Termination

59 |

If you breach any of these Terms, we have the right to suspend or disable your access to or use of the Service.

60 |

Assignment

61 |

We may assign or delegate these Terms of Service and/or the Privacy Policy, in whole or in part, to any person or entity at any time with or without your consent, including the license grant in the "Your Content & Conduct" section. You may not assign or delegate any rights or obligations under the Terms of Service or Privacy Policy without our prior written consent, and any unauthorized assignment and delegation by you is void.

62 |

Entire Agreement

63 |

These Terms constitute the entire agreement regarding the use of the Service, supersedes any prior agreements relating to your use of the Service.

64 |

Feedback

65 |

Please let us know what you think of the Service, these Terms and, in general, Pegao. When you provide us with any feedback, comments or suggestions about the Service, these Terms and, in general, Pegao, you irrevocably assign to us all of your right, title and interest in and to your feedback, comments and suggestions.

66 |

Questions & Contact Information

67 |

Questions or comments about the Service may be directed to us at the email address gonza@pegao.co.

68 |
69 |
70 | 83 | {% endblock %} 84 | -------------------------------------------------------------------------------- /dione/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /dione/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path #, re_path 2 | from . import views 3 | 4 | urlpatterns = [ 5 | #articles 6 | path('tour', views.tour ), 7 | path('getting-started', views.getting_started ), 8 | 9 | path('http', views.submit ), 10 | #re_path(r'^(?P.*)/$', views.submit ), 11 | #re_path(r'^(http|http(s)\?://[\w-]+\.)+(?P)?', views.submit ), 12 | 13 | #^(http|http(s)?://)?([\w-]+\.)+[\w-]+[.com|.in|.org]+(\[\?%&=]*)? 14 | #re_path(r'^http(?P.*)?(?P[\?%&=]*)/$', views.submit ),#/(?P[0-9]{2})/(?P[\w-]+)/$ 15 | #path('https://emojipedia.org/search/?q=round_pushpin', views.submit ), 16 | 17 | path('@/following', views.following ), 18 | path('@/list/', views.profilelist ), 19 | path('@/lists', views.profile ), 20 | path('@/emoji/', views.profileemoji ), 21 | path('@', views.profile ), 22 | 23 | path('', views.index ), 24 | path('home', views.index ), 25 | 26 | path('terms', views.terms ), 27 | path('privacy', views.privacy ), 28 | 29 | ] 30 | -------------------------------------------------------------------------------- /dione/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | from django.contrib.auth.decorators import login_required 3 | from django.shortcuts import redirect 4 | from django.conf import settings 5 | 6 | def index(request): 7 | debug_flag = settings.DEBUG 8 | context = {'debug_flag':debug_flag} 9 | 10 | if request.user.is_authenticated: 11 | return render(request, 'app/index.html', context) 12 | else: 13 | return render(request, 'index.html', context) 14 | 15 | #@login_required 16 | def profile(request,pk): 17 | debug_flag = settings.DEBUG 18 | context = {'debug_flag':debug_flag} 19 | return render(request, "app/index.html", context ) 20 | 21 | #@login_required 22 | def profilelist(request,pk,list): 23 | debug_flag = settings.DEBUG 24 | context = {'debug_flag':debug_flag} 25 | return render(request, "app/index.html", context ) 26 | 27 | #@login_required 28 | def profileemoji(request,pk,emoji): 29 | debug_flag = settings.DEBUG 30 | context = {'debug_flag':debug_flag} 31 | return render(request, "app/index.html", context ) 32 | 33 | #@login_required 34 | def following(request,pk): 35 | debug_flag = settings.DEBUG 36 | context = {'debug_flag':debug_flag} 37 | return render(request, "app/index.html", context ) 38 | 39 | @login_required 40 | def submit(request,url): 41 | debug_flag = settings.DEBUG 42 | context = {'debug_flag':debug_flag} 43 | return render(request, "app/index.html", context ) 44 | 45 | def terms(request): 46 | debug_flag = settings.DEBUG 47 | context = {'debug_flag':debug_flag} 48 | return render(request, "terms.html", context ) 49 | 50 | def privacy(request): 51 | debug_flag = settings.DEBUG 52 | context = {'debug_flag':debug_flag} 53 | return render(request, "privacy.html", context ) 54 | 55 | #articles 56 | def tour(request): 57 | debug_flag = settings.DEBUG 58 | context = {'debug_flag':debug_flag} 59 | return render(request, "articles/tour.html", context ) 60 | 61 | def getting_started(request): 62 | debug_flag = settings.DEBUG 63 | context = {'debug_flag':debug_flag} 64 | return render(request, "articles/how-do-i-paste-a-link.html", context ) 65 | -------------------------------------------------------------------------------- /hiperion/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zakokor/pegao/9e4d0a2c9dff74b808eefa05cfbe2b9bbe2058a2/hiperion/__init__.py -------------------------------------------------------------------------------- /hiperion/admin.py: -------------------------------------------------------------------------------- 1 | #from django.contrib import admin 2 | from django.contrib import admin 3 | from django.contrib.auth.admin import UserAdmin 4 | from .models import User 5 | 6 | admin.site.register(User, UserAdmin) 7 | -------------------------------------------------------------------------------- /hiperion/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class HiperionConfig(AppConfig): 5 | name = 'hiperion' 6 | -------------------------------------------------------------------------------- /hiperion/models.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.db import models 3 | from django.contrib.auth.models import AbstractUser 4 | from django.contrib.auth import get_user_model 5 | from django.utils.text import slugify 6 | 7 | #Transactions classes 8 | class User(AbstractUser): 9 | location = models.CharField(max_length=30) 10 | language = models.CharField(max_length=2) 11 | photo = models.ImageField(upload_to='photo/',null=True) 12 | cover = models.ImageField(upload_to='cover/',null=True) 13 | about = models.CharField(null=True,max_length=140) 14 | team_flag = models.BooleanField(default=False) 15 | 16 | class Follower(models.Model): 17 | class Meta: 18 | unique_together = ['user', 'follower'] #para que no pueda existir mas de un registro de una relacion ya creada 19 | 20 | STATUSES = ( 21 | ('following', 'following'), 22 | ('unfollow', 'unfollow'), 23 | ) 24 | 25 | id = models.AutoField(primary_key=True) 26 | 27 | user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE, related_name='users') 28 | follower = models.ForeignKey(get_user_model(), on_delete=models.CASCADE, related_name='followers') 29 | status = models.CharField(max_length=10, choices=STATUSES, default='follow') 30 | 31 | created_at = models.DateTimeField(auto_now_add=True) 32 | 33 | #para suscribirse a una fuente (ej elpais.com.co) 34 | class Suscription(models.Model): 35 | STATUSES = ( 36 | ('follow', 'follow'), 37 | ('unfollow', 'unfollow'), 38 | ) 39 | 40 | id = models.AutoField(primary_key=True) 41 | 42 | user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE) 43 | source = models.CharField(max_length=100) 44 | status = models.CharField(max_length=10, choices=STATUSES, default='follow') 45 | 46 | created_at = models.DateTimeField(auto_now_add=True) 47 | 48 | class Post(models.Model): 49 | STATUSES = ( 50 | ('active', 'active'), 51 | ('removed', 'removed'), 52 | ('blocked', 'blocked'), 53 | ) 54 | 55 | id = models.AutoField(primary_key=True) 56 | 57 | link = models.CharField(max_length=200) 58 | image = models.CharField(null=True,max_length=200) 59 | ico = models.CharField(null=True,max_length=100) 60 | emoji = models.CharField(null=True,max_length=100) 61 | text = models.TextField() 62 | slug = models.SlugField(default='',editable=False,max_length=200) 63 | tags = models.CharField(null=True,max_length=100) 64 | votes = models.SmallIntegerField(default=0) 65 | views = models.SmallIntegerField(default=0) 66 | repost = models.SmallIntegerField(default=0) 67 | badge = models.SmallIntegerField(null=True) 68 | points = models.SmallIntegerField(default=5) 69 | status = models.CharField(max_length=10, choices=STATUSES, default='active') 70 | list = models.CharField(null=True,max_length=30) 71 | author = models.ForeignKey(get_user_model(), on_delete=models.CASCADE, related_name='authors') 72 | 73 | created_at = models.DateTimeField(auto_now_add=True,db_index=True) 74 | 75 | """def get_absolute_url(self): 76 | return '/l/'+self.slug #reverse('post', args=[str(self.id)]) 77 | """ 78 | 79 | def save(self, *args, **kwargs): 80 | value = self.text 81 | self.slug = slugify(value, allow_unicode=True) 82 | super().save(*args, **kwargs) 83 | 84 | class Activity(models.Model): 85 | STATUSES = ( 86 | ('vote', 'vote'), 87 | ('viewed', 'viewed'), 88 | ('repost', 'repost'), 89 | ('mark', 'mark'), 90 | ('hide', 'hide'), 91 | ('spam', 'spam'), 92 | ('ilegal', 'ilegal'), 93 | ) 94 | 95 | id = models.AutoField(primary_key=True) 96 | 97 | post = models.ForeignKey('Post', on_delete=models.CASCADE) 98 | user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE) 99 | 100 | status = models.CharField(max_length=10, choices=STATUSES) 101 | 102 | created_at = models.DateTimeField(auto_now_add=True) 103 | 104 | class Meta: 105 | unique_together = ('post', 'user', 'status') 106 | -------------------------------------------------------------------------------- /hiperion/pipeline.py: -------------------------------------------------------------------------------- 1 | from urllib.request import urlopen 2 | from django.core.files.base import ContentFile 3 | 4 | def get_avatar(backend, strategy, details, response, 5 | user=None, *args, **kwargs): 6 | 7 | if not kwargs['is_new']: 8 | return 9 | 10 | url = None 11 | """if backend.name == 'facebook': 12 | url = "http://graph.facebook.com/%s/picture?type=large"%response['id'] 13 | if backend.name == 'twitter': 14 | url = response.get('profile_image_url', '').replace('_normal','')""" 15 | 16 | if backend.name == 'google-oauth2': 17 | if 'picture' in response and 'sub' in response: 18 | url = response['picture'] 19 | image_name = response['sub'] 20 | image_ext = url.split('.')[-1] 21 | if url: 22 | user.photo.save( 23 | '{0}.{1}'.format(image_name, image_ext), 24 | ContentFile(urlopen(url).read()), 25 | save=False 26 | ) 27 | user.save() 28 | 29 | -------------------------------------------------------------------------------- /hiperion/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | from hiperion.models import * 3 | from django.db.models import F 4 | 5 | class UserSerializer(serializers.ModelSerializer): 6 | 7 | fullname = serializers.SerializerMethodField() 8 | username = serializers.SerializerMethodField() 9 | 10 | def get_fullname(self, obj): #se ejecuta en el GET para trer el nombre de usuario 11 | return ' '.join(filter(bool, (obj.first_name, obj.last_name))) 12 | 13 | def get_username(self, obj): #se ejecuta en el GET para trer el nombre de usuario 14 | return obj.username 15 | 16 | class Meta: 17 | model = User 18 | fields = ('username','location','language','photo','cover','fullname','about','team_flag') 19 | 20 | #Consultar el listado de listas por usuario 21 | class UserListSerializer(serializers.ModelSerializer): 22 | 23 | username = serializers.SerializerMethodField() 24 | 25 | def get_username(self, obj): #se ejecuta en el GET para traer el nombre de usuario 26 | return obj.author.username 27 | 28 | class Meta: 29 | model = Post 30 | fields = ('username','list', ) 31 | 32 | class FollowerSerializer(serializers.ModelSerializer): 33 | 34 | follower = serializers.SerializerMethodField() 35 | following = serializers.SerializerMethodField() 36 | 37 | def get_follower(self, obj): #se ejecuta en el GET para trer el nombre de usuario 38 | return obj.follower.username 39 | def get_following(self, obj): #se ejecuta en el GET para trer el nombre de usuario 40 | return obj.user.username 41 | 42 | class Meta: 43 | model = Follower 44 | exclude = ('id','user', ) 45 | 46 | class PostSerializer(serializers.ModelSerializer): 47 | 48 | fullname = serializers.SerializerMethodField() 49 | username = serializers.SerializerMethodField() 50 | photo = serializers.SerializerMethodField() 51 | viewed = serializers.SerializerMethodField() 52 | voted = serializers.SerializerMethodField() 53 | reposted = serializers.SerializerMethodField() 54 | 55 | def get_fullname(self, obj): #se ejecuta en el GET para trer el nombre de usuario 56 | return ' '.join(filter(bool, (obj.author.first_name, obj.author.last_name))) 57 | 58 | def get_username(self, obj): #se ejecuta en el GET para traer el nombre de usuario 59 | return obj.author.username 60 | 61 | def get_photo(self, obj): #se ejecuta en el GET para traer la foto de usuario 62 | if obj.author.photo: 63 | return obj.author.photo.url 64 | else: 65 | return None 66 | 67 | def get_viewed(self, obj): #se ejecuta en el GET para traer si el usuario ya visito el post 68 | viewed = False 69 | 70 | if self.context['request'].user.is_authenticated: 71 | activity = Activity.objects.filter(post=obj.id,status='viewed',user=self.context['request'].user).count() 72 | if activity > 0: 73 | viewed = True 74 | 75 | return viewed 76 | 77 | def get_reposted(self, obj): #se ejecuta en el GET para traer si el usuario ya voto por el post 78 | reposted = False 79 | 80 | if self.context['request'].user.is_authenticated: 81 | activity = Activity.objects.filter(post=obj.id,status='repost',user=self.context['request'].user).count() 82 | if activity > 0: 83 | reposted = True 84 | 85 | return reposted 86 | 87 | def get_voted(self, obj): #se ejecuta en el GET para traer si el usuario ya voto por el post 88 | voted = False 89 | 90 | if self.context['request'].user.is_authenticated: 91 | activity = Activity.objects.filter(post=obj.id,status='vote',user=self.context['request'].user).count() 92 | if activity > 0: 93 | voted = True 94 | 95 | return voted 96 | 97 | class Meta: 98 | model = Post 99 | exclude = ('author', ) 100 | read_only_fields = ('votes','views','repost','badge','points','status','author','list') 101 | #ordering = ['-votes']#'-created_at', 102 | 103 | class ActivityViewSerializer(serializers.ModelSerializer): 104 | 105 | username = serializers.SerializerMethodField() 106 | 107 | def get_username(self, obj): #se ejecuta en el GET para trer el nombre de usuario 108 | return obj.user.username 109 | 110 | class Meta: 111 | model = Activity 112 | 113 | exclude = ('user', ) 114 | 115 | def create(self, validated_data): #guardar un voto nuevo 116 | activity, created = Activity.objects.get_or_create(**validated_data) #si no existe lo crea (created=true), de lo contrario lo devuelve (created=false) 117 | 118 | if created: #si se creo un voto nuevo, suma + 1 en el post 119 | post = Post.objects.select_related().filter(id=self.initial_data.get("post")).update(views=F('views')+1) 120 | 121 | return activity 122 | 123 | class ActivityVoteSerializer(serializers.ModelSerializer): 124 | 125 | username = serializers.SerializerMethodField() 126 | 127 | def get_username(self, obj): #se ejecuta en el GET para trer el nombre de usuario 128 | return obj.user.username 129 | 130 | class Meta: 131 | model = Activity 132 | 133 | exclude = ('user', ) 134 | 135 | def create(self, validated_data): #guardar un voto nuevo 136 | activity, created = Activity.objects.get_or_create(**validated_data) #si no existe lo crea (created=true), de lo contrario lo devuelve (created=false) 137 | 138 | if created: #si se creo un voto nuevo, suma + 1 en el post 139 | post = Post.objects.select_related().filter(id=self.initial_data.get("post")).update(votes=F('votes')+1) 140 | 141 | return activity 142 | 143 | class ActivityRePostSerializer(serializers.ModelSerializer): 144 | 145 | username = serializers.SerializerMethodField() 146 | 147 | def get_username(self, obj): #se ejecuta en el GET para trer el nombre de usuario 148 | return obj.user.username 149 | 150 | class Meta: 151 | model = Activity 152 | 153 | exclude = ('user', ) 154 | 155 | def create(self, validated_data): #guardar un voto nuevo 156 | activity, created = Activity.objects.get_or_create(**validated_data) #si no existe lo crea (created=true), de lo contrario lo devuelve (created=false) 157 | 158 | if created: #si se creo un voto nuevo, suma + 1 en el post 159 | post = Post.objects.select_related().filter(id=self.initial_data.get("post")).update(repost=F('repost')+1) 160 | 161 | return activity 162 | -------------------------------------------------------------------------------- /hiperion/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /hiperion/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from . import views 3 | 4 | urlpatterns = [ 5 | #post 6 | path('api/posts/submit', views.Submit.as_view() ), 7 | path('api/posts/', views.PostList.as_view() ), #listar todos los posts o crear uno nuevo 8 | path('api/posts/update', views.PostCreate.as_view() ), #listar todos los posts o crear uno nuevo 9 | path('api/posts/recent', views.RecentPostsList.as_view() ), #listar todos los posts o crear uno nuevo 10 | path('api/posts/', views.PostList.as_view() ), #listar todos los posts del usuario 11 | path('api/posts//emojis/', views.PostUserEmoji.as_view() ), #listar todos los posts del usuario para este emoji 12 | path('api/posts//lists/', views.PostUserList.as_view() ), #listar todos los posts de una lista del usuario 13 | 14 | path('api/activities//view', views.PostActivityView.as_view() ), 15 | path('api/activities//vote', views.PostActivityVote.as_view() ), 16 | path('api/activities//vote/destroy', views.PostActivityVoteDestroy.as_view() ), 17 | path('api/activities//repost', views.PostActivityRePost.as_view() ), 18 | path('api/activities//repost/destroy', views.PostActivityRePostDestroy.as_view() ), 19 | 20 | #user api 21 | path('api/friendships/', views.FriendshipListCreate.as_view() ), #lista y crea una relacion como seguidor del usuario logeado con :username 22 | path('api/friendships//destroy', views.FriendshipDestroy.as_view() ), #elimina una relacion como seguidor del usuario logeado con :username 23 | 24 | #convertir url como api/users//following 25 | #path('api/following/', views.FollowingList.as_view() ), #lista los usuarios que sigue :username 26 | #path('api/followers/', views.FollowerListCreate.as_view() ), #lista los seguidores de :username 27 | 28 | #user api 29 | path('api/users/me', views.CurrentUserRetrieve.as_view() ), #listar info de un usuario 30 | path('api/users/top', views.TopUsersList.as_view() ), #listar info de un usuario 31 | path('api/users/', views.UserRetrieve.as_view() ), #listar info de un usuario 32 | path('api/users//lists', views.UserList.as_view() ), #listar todas listas del usuario 33 | ] 34 | -------------------------------------------------------------------------------- /hiperion/views.py: -------------------------------------------------------------------------------- 1 | #from django.shortcuts import render 2 | from rest_framework.permissions import IsAuthenticated 3 | from rest_framework import generics 4 | from rest_framework import status 5 | from rest_framework.exceptions import NotFound 6 | from rest_framework.views import APIView 7 | from rest_framework.renderers import JSONRenderer 8 | from rest_framework.response import Response 9 | from rest_framework.pagination import PageNumberPagination 10 | from urllib.request import urlopen, Request 11 | from urllib.parse import urlsplit, quote, urlunsplit 12 | from bs4 import BeautifulSoup 13 | from django.shortcuts import get_object_or_404 14 | import re 15 | 16 | from hiperion.models import * 17 | from hiperion.serializers import * 18 | 19 | import requests 20 | 21 | class CustomPagination(PageNumberPagination): 22 | page_size = 30 23 | page_size_query_param = 'page_size' 24 | max_page_size = 100 25 | 26 | def get_paginated_response(self, data): 27 | if not self.page.has_next(): 28 | page_number = None 29 | else: 30 | page_number = self.page.next_page_number() 31 | 32 | return Response({ 33 | 'next': page_number, #self.get_next_link(), 34 | 'count': self.page.paginator.count, 35 | 'size': self.page_size, 36 | 'results': data 37 | }) 38 | 39 | class Submit(APIView): 40 | permission_classes = (IsAuthenticated,) #restringue a que solo se pueda consumir si un usuario está logeado 41 | 42 | renderer_classes = (JSONRenderer, ) 43 | 44 | def get(self, request, format=None): 45 | url = self.request.query_params.get('url') 46 | 47 | #inicio para link con caracteres especiales como tildes, etc 48 | url = urlsplit(url) 49 | #print("url1",url) 50 | url = list(url) 51 | #print("url2",url) 52 | url[2] = quote(url[2]) 53 | #print("url3",url) 54 | url = urlunsplit(url) 55 | print("url4",url) 56 | #fin para link con caracteres especiales como tildes, etc 57 | 58 | """ 59 | #inicio metodo 1 60 | #para paginas que piden un agente y no genere HTTP Error 403: Forbidden 61 | req = Request(url, headers={'User-Agent': 'Mozilla/5.0'}) 62 | #req = Request(url, headers=headers) 63 | 64 | #html = urlopen(url) 65 | html = urlopen(req) #.read() 66 | #print("html",html) 67 | 68 | soup = BeautifulSoup(html, 'html.parser') 69 | print("soup.title",soup.title) 70 | print("find title",soup.find('title')) 71 | #fin metodo 1 72 | """ 73 | 74 | #inicio metodo 2 75 | headers = requests.utils.default_headers() 76 | headers.update({ 77 | 'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0', 78 | }) 79 | 80 | r = requests.get(url, headers) 81 | raw_html = r.content 82 | #print("raw_html",raw_html) 83 | soup = BeautifulSoup(raw_html, 'html.parser') 84 | #fin metodo 2 85 | 86 | if soup.title: 87 | content = {'url': url, 'title': soup.title.string } 88 | else: 89 | content = {'url': url, 'title': '' } 90 | 91 | 92 | #content = {'url': url, 'title': soup.title.string } 93 | return Response(content) 94 | #return Response({}) 95 | 96 | 97 | class PostCreate(generics.CreateAPIView): 98 | permission_classes = (IsAuthenticated,) #restringue a que solo se pueda consumir si un usuario está logeado 99 | 100 | serializer_class = PostSerializer 101 | #pagination_class = CustomPagination #implementation custom pagination 102 | 103 | def perform_create(self, serializer): #se ejecuta en el POST para crear una publicacion 104 | list = re.findall(r"(?:^|\s)[/]{1}(\w+)", self.request.data["text"], re.UNICODE) #debe iniciar despues de un espacio por ej. mi carro /hola 105 | 106 | serializer.save( 107 | author=self.request.user, list=list[len(list)-1] if len(list) > 0 else None) #,emoji=":%s:" % self.request.data["emoji"]) 108 | 109 | 110 | class PostList(generics.ListAPIView): 111 | #permission_classes = (IsAuthenticated,) #restringue a que solo se pueda consumir si un usuario está logeado 112 | 113 | serializer_class = PostSerializer 114 | pagination_class = CustomPagination #implementation custom pagination 115 | 116 | def get_queryset(self): #consultar 117 | if self.kwargs is not None and 'username' in self.kwargs: #users posts 118 | user_id = User.objects.get(username=self.kwargs["username"]) 119 | 120 | queryset = Post.objects.filter(author=user_id).order_by('-created_at','-votes') 121 | 122 | else: # user feed 123 | 124 | user_id = self.request.user 125 | 126 | queryset = Post.objects.filter(author=user_id).order_by('-created_at','-votes') 127 | 128 | #follower_ids = Follower.objects.filter(follower=self.request.user).values_list('user',flat=True) 129 | follower_ids = Follower.objects.filter(follower=user_id).values_list('user',flat=True) 130 | 131 | if follower_ids: 132 | queryset = queryset | Post.objects.filter(author_id__in=follower_ids) 133 | 134 | """suscription_ids = Suscription.objects.filter(user=user_id).values_list('source',flat=True) 135 | if suscription_ids: 136 | #print("hola suscription_ids") 137 | 138 | queryset = queryset | Post.objects.filter(link__contains=suscription_ids) 139 | #print('query2:',queryset.query) 140 | """ 141 | 142 | return queryset 143 | 144 | """def dispatch(self, *args, **kwargs): 145 | response = super().dispatch(*args, **kwargs) 146 | # For debugging purposes only. 147 | from django.db import connection 148 | 149 | print('# of Queries: {}'.format(len(connection.queries))) 150 | #for query in connection.queries: 151 | # print(query['sql']) 152 | 153 | return response """ 154 | 155 | class RecentPostsList(generics.ListAPIView): 156 | #permission_classes = (IsAuthenticated,) #restringue a que solo se pueda consumir si un usuario está logeado 157 | 158 | serializer_class = PostSerializer 159 | pagination_class = CustomPagination #implementation custom pagination 160 | 161 | def get_queryset(self): #consultar 162 | 163 | queryset = Post.objects.order_by('-created_at','-votes')[:30] 164 | 165 | return queryset 166 | 167 | 168 | class PostUserList(generics.ListAPIView): 169 | #permission_classes = (IsAuthenticated,) #restringue a que solo se pueda consumir si un usuario está logeado 170 | 171 | serializer_class = PostSerializer 172 | pagination_class = CustomPagination #implementation custom pagination 173 | 174 | def get_queryset(self): #consultar 175 | 176 | if self.kwargs is not None and ('username' and 'list') in self.kwargs: 177 | print("filtro") 178 | 179 | user_id = User.objects.get(username=self.kwargs["username"]) 180 | list_id = self.kwargs["list"] 181 | 182 | queryset = Post.objects.filter(author=user_id,list=list_id).order_by('-created_at','-votes') 183 | 184 | return queryset 185 | 186 | class PostUserEmoji(generics.ListAPIView): 187 | #permission_classes = (IsAuthenticated,) #restringue a que solo se pueda consumir si un usuario está logeado 188 | 189 | serializer_class = PostSerializer 190 | pagination_class = CustomPagination #implementation custom pagination 191 | 192 | def get_queryset(self): #consultar 193 | 194 | if self.kwargs is not None and ('username' and 'emoji') in self.kwargs: 195 | print("PostUserEmoji") 196 | 197 | user_id = User.objects.get(username=self.kwargs["username"]) 198 | emoji_id = self.kwargs["emoji"] 199 | 200 | queryset = Post.objects.filter(author=user_id,emoji=emoji_id).order_by('-created_at','-votes') 201 | 202 | return queryset 203 | 204 | class PostActivityView(generics.CreateAPIView): 205 | permission_classes = (IsAuthenticated,) #restringue a que solo se pueda consumir si un usuario está logeado 206 | 207 | serializer_class = ActivityViewSerializer 208 | 209 | def perform_create(self, serializer): #se ejecuta en el POST para crear una publicacion 210 | serializer.save( 211 | user=self.request.user) 212 | 213 | class PostActivityVote(generics.CreateAPIView): 214 | permission_classes = (IsAuthenticated,) #restringue a que solo se pueda consumir si un usuario está logeado 215 | 216 | serializer_class = ActivityVoteSerializer 217 | 218 | def perform_create(self, serializer): #se ejecuta en el POST para crear una publicacion 219 | serializer.save( 220 | user=self.request.user) 221 | 222 | class PostActivityVoteDestroy(generics.DestroyAPIView): 223 | permission_classes = (IsAuthenticated,) #restringue a que solo se pueda consumir si un usuario está logeado 224 | 225 | serializer_class = ActivityVoteSerializer 226 | 227 | def get_object(self): #para sobreescribir el parametro pk por el filtro implementado en get_queryset 228 | queryset = self.get_queryset() 229 | obj = get_object_or_404(queryset) 230 | 231 | return obj 232 | 233 | def get_queryset(self): #busca el registro con el filtro personalizado 234 | 235 | queryset = Activity.objects.filter(post=self.kwargs["post"],status='vote',user=self.request.user) 236 | 237 | return queryset 238 | 239 | def destroy(self, request, *args, **kwargs): #elimina un voto 240 | try: 241 | #print("delete",self.kwargs["post"]) 242 | instance = self.get_object() 243 | 244 | post = Post.objects.select_related().filter(id=self.kwargs["post"]).update(votes=F('votes')-1) #resta -1 voto del post 245 | 246 | self.perform_destroy(instance) 247 | except Http404: 248 | pass 249 | return Response(status=status.HTTP_204_NO_CONTENT) #si no ocurre error 250 | 251 | class PostActivityRePost(generics.CreateAPIView): 252 | permission_classes = (IsAuthenticated,) #restringue a que solo se pueda consumir si un usuario está logeado 253 | 254 | serializer_class = ActivityRePostSerializer 255 | 256 | def perform_create(self, serializer): #se ejecuta en el POST para crear una publicacion 257 | serializer.save( 258 | user=self.request.user) 259 | 260 | class PostActivityRePostDestroy(generics.DestroyAPIView): 261 | permission_classes = (IsAuthenticated,) #restringue a que solo se pueda consumir si un usuario está logeado 262 | 263 | serializer_class = ActivityRePostSerializer 264 | 265 | def get_object(self): #para sobreescribir el parametro pk por el filtro implementado en get_queryset 266 | queryset = self.get_queryset() 267 | obj = get_object_or_404(queryset) 268 | 269 | return obj 270 | 271 | def get_queryset(self): #busca el registro con el filtro personalizado 272 | 273 | queryset = Activity.objects.filter(post=self.kwargs["post"],status='repost',user=self.request.user) 274 | 275 | return queryset 276 | 277 | def destroy(self, request, *args, **kwargs): #elimina un voto 278 | try: 279 | #print("delete",self.kwargs["post"]) 280 | instance = self.get_object() 281 | 282 | post = Post.objects.select_related().filter(id=self.kwargs["post"]).update(repost=F('repost')-1) #resta -1 voto del post 283 | 284 | self.perform_destroy(instance) 285 | except Http404: 286 | pass 287 | return Response(status=status.HTTP_204_NO_CONTENT) #si no ocurre error 288 | 289 | class TopUsersList(generics.ListAPIView): 290 | #permission_classes = (IsAuthenticated,) #restringue a que solo se pueda consumir si un usuario está logeado 291 | 292 | serializer_class = UserSerializer 293 | #pagination_class = None 294 | 295 | def get_queryset(self): #consultar 296 | 297 | activity_ids = Post.objects.values_list('author',flat=True).annotate(cnt=models.Count('id')).filter(cnt__gte=3) #consulta los usuarios con 3 o mas posts 298 | 299 | queryset = User.objects.filter(pk__in=list(activity_ids))[:30] 300 | 301 | return queryset 302 | 303 | 304 | class CurrentUserRetrieve(generics.RetrieveAPIView): 305 | #permission_classes = (IsAuthenticated,) #restringue a que solo se pueda consumir si un usuario está logeado 306 | 307 | serializer_class = UserSerializer 308 | 309 | def get_object(self): #para sobreescribir el parametro pk por el filtro implementado en get_queryset 310 | queryset = self.get_queryset() 311 | obj = get_object_or_404(queryset) 312 | 313 | return obj 314 | 315 | def get_queryset(self): #busca el registro con el filtro personalizado 316 | print("self.request.user",self.request.user.id) 317 | 318 | queryset = User.objects.filter(pk=self.request.user.id) 319 | 320 | return queryset 321 | 322 | class UserRetrieve(generics.RetrieveAPIView): 323 | #permission_classes = (IsAuthenticated,) #restringue a que solo se pueda consumir si un usuario está logeado 324 | 325 | serializer_class = UserSerializer 326 | 327 | def get_object(self): #para sobreescribir el parametro pk por el filtro implementado en get_queryset 328 | queryset = self.get_queryset() 329 | obj = get_object_or_404(queryset) 330 | 331 | return obj 332 | 333 | def get_queryset(self): #busca el registro con el filtro personalizado 334 | #print("self.request.user",self.request.user.id) 335 | if self.kwargs is not None and 'username' in self.kwargs: 336 | user_id = User.objects.get(username=self.kwargs["username"]) 337 | 338 | queryset = User.objects.filter(pk=user_id.id) 339 | 340 | return queryset 341 | 342 | class UserList(generics.ListAPIView): 343 | #permission_classes = (IsAuthenticated,) #restringue a que solo se pueda consumir si un usuario está logeado 344 | 345 | serializer_class = UserListSerializer 346 | #pagination_class = None 347 | 348 | def get_queryset(self): #consultar 349 | 350 | if self.kwargs is not None and 'username' in self.kwargs: 351 | print("filtro") 352 | 353 | user_id = User.objects.get(username=self.kwargs["username"]) 354 | 355 | queryset = Post.objects.filter(author=user_id,list__isnull=False).distinct('list').order_by('list') 356 | 357 | return queryset 358 | 359 | class FriendshipListCreate(generics.ListCreateAPIView): 360 | permission_classes = (IsAuthenticated,) #restringue a que solo se pueda consumir si un usuario está logeado 361 | 362 | serializer_class = FollowerSerializer 363 | 364 | def perform_create(self, serializer): #se ejecuta en el POST cuando crea un registro en bd 365 | if self.kwargs is not None and 'username' in self.kwargs: 366 | user_id = User.objects.get(username=self.kwargs["username"]) 367 | 368 | serializer.save( 369 | follower=self.request.user, user=user_id) 370 | 371 | def list(self, request, *args, **kwargs): #se ejecuta el select personalizado 372 | 373 | if self.kwargs is not None and 'username' in self.kwargs: 374 | if self.request.user.username == self.kwargs["username"]: #valida que un usuario no se pueda seguir a si mismo 375 | return Response(status=status.HTTP_204_NO_CONTENT) 376 | 377 | serializer_data = self.get_queryset() 378 | serializer = self.serializer_class(serializer_data, many=True) 379 | 380 | return Response(serializer.data, status=status.HTTP_200_OK) 381 | 382 | def get_queryset(self): #consultar 383 | 384 | if self.kwargs is not None and 'username' in self.kwargs: 385 | user_id = User.objects.get(username=self.kwargs["username"]) 386 | 387 | queryset = Follower.objects.filter(user=user_id,follower=self.request.user) 388 | 389 | return queryset 390 | 391 | class FriendshipDestroy(generics.DestroyAPIView): 392 | permission_classes = (IsAuthenticated,) #restringue a que solo se pueda consumir si un usuario está logeado 393 | 394 | serializer_class = FollowerSerializer 395 | 396 | def get_object(self): #para sobreescribir el parametro pk por el filtro user y follower implementado en get_queryset 397 | queryset = self.get_queryset() 398 | obj = get_object_or_404(queryset) 399 | 400 | return obj 401 | 402 | def get_queryset(self): #filtra el registro a eliminar buscando por la combinación user y follower 403 | 404 | if self.kwargs is not None and 'username' in self.kwargs: 405 | user_id = User.objects.get(username=self.kwargs["username"]) 406 | 407 | queryset = Follower.objects.filter(user=user_id,follower=self.request.user) 408 | 409 | return queryset 410 | 411 | class FollowingList(generics.ListAPIView): 412 | permission_classes = (IsAuthenticated,) #restringue a que solo se pueda consumir si un usuario está logeado 413 | 414 | serializer_class = FollowerSerializer 415 | 416 | def get_queryset(self): #consultar 417 | 418 | if self.kwargs is not None and 'username' in self.kwargs: 419 | user_id = User.objects.get(username=self.kwargs["username"]) 420 | 421 | queryset = Follower.objects.filter(follower=user_id) 422 | 423 | return queryset 424 | -------------------------------------------------------------------------------- /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 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'voyager.settings') 9 | try: 10 | from django.core.management import execute_from_command_line 11 | except ImportError as exc: 12 | raise ImportError( 13 | "Couldn't import Django. Are you sure it's installed and " 14 | "available on your PYTHONPATH environment variable? Did you " 15 | "forget to activate a virtual environment?" 16 | ) from exc 17 | execute_from_command_line(sys.argv) 18 | 19 | 20 | if __name__ == '__main__': 21 | main() 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pegao", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "webpack --mode development", 8 | "dev2": "webpack -p --json --progress --profile > webpack-build-log.json", 9 | "build": "webpack --mode production", 10 | "watch": "npm run dev -- --watch" 11 | }, 12 | "keywords": [], 13 | "author": "", 14 | "license": "ISC", 15 | "devDependencies": { 16 | "@babel/core": "^7.6.2", 17 | "@babel/preset-env": "^7.6.2", 18 | "@babel/preset-react": "^7.0.0", 19 | "axios": "^0.21.1", 20 | "babel-loader": "^8.0.6", 21 | "babel-plugin-transform-class-properties": "^6.24.1", 22 | "bulma": "^0.7.5", 23 | "css-loader": "^3.2.0", 24 | "glob-all": "^3.1.0", 25 | "lodash.throttle": "^4.1.1", 26 | "mini-css-extract-plugin": "^0.8.0", 27 | "node-sass": "^4.12.0", 28 | "optimize-css-assets-webpack-plugin": "^5.0.3", 29 | "purgecss-webpack-plugin": "^1.6.0", 30 | "query-string": "^6.8.3", 31 | "react": "^16.10.0", 32 | "react-contenteditable": "^3.2.6", 33 | "react-dom": "^16.10.0", 34 | "react-router-dom": "^5.2.0", 35 | "sass-loader": "^10.0.2", 36 | "style-loader": "^1.0.0", 37 | "terser-webpack-plugin": "^2.1.0", 38 | "weak-key": "^1.0.1", 39 | "webpack": "^4.41.0", 40 | "webpack-bundle-analyzer": "^3.5.1", 41 | "webpack-cli": "^3.3.9" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | beautifulsoup4==4.7.1 2 | bugsnag==3.5.2 3 | certifi==2019.3.9 4 | chardet==3.0.4 5 | defusedxml==0.6.0 6 | Django==2.2.20 7 | djangorestframework==3.11.2 8 | idna==2.8 9 | oauthlib==3.0.1 10 | Pillow==8.1.1 11 | pkg-resources==0.0.0 12 | psycopg2-binary==2.8.1 13 | PyJWT==1.7.1 14 | python3-openid==3.1.0 15 | pytz==2018.9 16 | requests==2.21.0 17 | requests-oauthlib==1.2.0 18 | six==1.12.0 19 | social-auth-app-django==3.1.0 20 | social-auth-core==3.1.0 21 | soupsieve==1.9.1 22 | sqlparse==0.3.0 23 | urllib3==1.24.3 24 | WebOb==1.8.5 25 | -------------------------------------------------------------------------------- /templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {% load static %} 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | {% if debug_flag %} 31 | 32 | {% else %} 33 | 34 | {% endif %} 35 | 36 | {% block title %}Pegao - Save your links, it's free and social{% endblock %} 37 | 38 | 39 | {% block header %} 40 | 126 | {% endblock %} 127 | 128 | {% block content %}{% endblock %} 129 | 130 | 152 | 153 | 154 | {% block script %} 155 | 156 | 157 | 163 | {% endblock %} 164 | 165 | -------------------------------------------------------------------------------- /templates/registration/logged_out.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
5 |
6 |

Click here to login again

7 |

8 | 9 | 10 | 11 | 12 | Log in with Google 13 | 14 |

15 |

16 | By using Pegao, you agree to our Privacy policy and Terms of service. 17 |

18 |
19 |
20 | {% endblock %} -------------------------------------------------------------------------------- /templates/registration/login.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}Iniciar sesión en Pegao{% endblock %} 4 | 5 | {% block content %} 6 |
7 |
8 |

Save your links, it's free and social.

9 |

Log in to start

10 |

11 | 12 | 13 | 14 | 15 | Log in with Google 16 | 17 |

18 |

19 | By using Pegao, you agree to our Privacy policy and Terms of service. 20 |

21 |
22 |
23 | {% endblock %} -------------------------------------------------------------------------------- /voyager/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zakokor/pegao/9e4d0a2c9dff74b808eefa05cfbe2b9bbe2058a2/voyager/__init__.py -------------------------------------------------------------------------------- /voyager/registry.py: -------------------------------------------------------------------------------- 1 | REG_SECRET_KEY="" 2 | REG_DEBUG="True or False" 3 | REG_ALLOWED_HOSTS="A list of strings representing the host/domain names that this Django site can serve" 4 | REG_DATABASE_NAME="Database name" 5 | REG_DATABASE_USER="Username" 6 | REG_DATABASE_PW="Password" 7 | REG_DATABASE_HOST="localhost" 8 | BUGSNAG_API_KEY="" 9 | -------------------------------------------------------------------------------- /voyager/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for voyager project. 3 | 4 | Generated by 'django-admin startproject' using Django 2.2. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.2/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/2.2/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | try: 16 | from voyager.registry import * 17 | except ImportError: 18 | raise Exception("A registry file is required to run this project!") 19 | 20 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 21 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 22 | 23 | # Quick-start development settings - unsuitable for production 24 | # See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/ 25 | 26 | # SECURITY WARNING: keep the secret key used in production secret! 27 | SECRET_KEY = REG_SECRET_KEY 28 | 29 | 30 | # SECURITY WARNING: don't run with debug turned on in production! 31 | DEBUG = REG_DEBUG 32 | 33 | ALLOWED_HOSTS = REG_ALLOWED_HOSTS 34 | 35 | 36 | # Application definition 37 | 38 | INSTALLED_APPS = [ 39 | 'django.contrib.admin', 40 | 'django.contrib.auth', 41 | 'django.contrib.contenttypes', 42 | 'django.contrib.sessions', 43 | 'django.contrib.messages', 44 | 'django.contrib.staticfiles', 45 | 'django.contrib.sitemaps', 46 | 'dione', 47 | 'hiperion', 48 | 'rest_framework', 49 | 'social_django', 50 | ] 51 | 52 | MIDDLEWARE = [ 53 | 'bugsnag.django.middleware.BugsnagMiddleware', 54 | 'django.middleware.security.SecurityMiddleware', 55 | 'django.contrib.sessions.middleware.SessionMiddleware', 56 | 'django.middleware.common.CommonMiddleware', 57 | 'django.middleware.csrf.CsrfViewMiddleware', 58 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 59 | 'django.contrib.messages.middleware.MessageMiddleware', 60 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 61 | ] 62 | 63 | 64 | ROOT_URLCONF = 'voyager.urls' 65 | 66 | TEMPLATES = [ 67 | { 68 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 69 | 'DIRS': [os.path.join(BASE_DIR, 'templates')], 70 | 'APP_DIRS': True, 71 | 'OPTIONS': { 72 | 'context_processors': [ 73 | 'django.template.context_processors.debug', 74 | 'django.template.context_processors.request', 75 | 'django.contrib.auth.context_processors.auth', 76 | 'django.contrib.messages.context_processors.messages', 77 | 'social_django.context_processors.backends', #para usar OAuth 78 | 'social_django.context_processors.login_redirect', #para usar OAuth 79 | ], 80 | }, 81 | }, 82 | ] 83 | 84 | WSGI_APPLICATION = 'voyager.wsgi.application' 85 | 86 | 87 | # Database 88 | # https://docs.djangoproject.com/en/2.2/ref/settings/#databases 89 | 90 | DATABASES = { 91 | 'default': { 92 | 'ENGINE': 'django.db.backends.postgresql_psycopg2', #'django.db.backends.sqlite3', 93 | 'NAME': REG_DATABASE_NAME, 94 | 'USER': REG_DATABASE_USER, 95 | 'PASSWORD': REG_DATABASE_PW, 96 | 'HOST': REG_DATABASE_HOST, 97 | 'PORT': '', 98 | } 99 | } 100 | 101 | 102 | # Password validation 103 | # https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators 104 | 105 | AUTH_PASSWORD_VALIDATORS = [ 106 | { 107 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 108 | }, 109 | { 110 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 111 | }, 112 | { 113 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 114 | }, 115 | { 116 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 117 | }, 118 | ] 119 | 120 | 121 | # Internationalization 122 | # https://docs.djangoproject.com/en/2.2/topics/i18n/ 123 | 124 | LANGUAGE_CODE = 'en-us' 125 | 126 | TIME_ZONE = 'UTC' 127 | 128 | USE_I18N = True 129 | 130 | USE_L10N = True 131 | 132 | USE_TZ = True 133 | 134 | 135 | # Static files (CSS, JavaScript, Images) 136 | # https://docs.djangoproject.com/en/2.2/howto/static-files/ 137 | 138 | STATIC_URL = '/static/' 139 | 140 | #Gonzalo A. Feb 2019 141 | STATIC_ROOT = os.path.join(BASE_DIR, 'static/') #se requiere para Nginx 142 | 143 | MEDIA_URL = '/media/' #archivos cargados por ej foto de perfil 144 | MEDIA_ROOT = os.path.join(BASE_DIR, 'media') 145 | 146 | AUTH_USER_MODEL = 'hiperion.User' 147 | 148 | LOGIN_URL = 'login' 149 | LOGIN_REDIRECT_URL = '/' #cuando se logee va a la raiz 150 | #LOGOUT_REDIRECT_URL = '/' 151 | 152 | #para usar OAuth 153 | AUTHENTICATION_BACKENDS = ( 154 | 'social_core.backends.google.GoogleOAuth2', 155 | 'django.contrib.auth.backends.ModelBackend', #para poder seguir usando la clave y usuario y poder ingresar al sitio admin 156 | ) 157 | 158 | SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = '65545908578-rdnfspb07n77lf4g4iubvc29u4kq85kf.apps.googleusercontent.com' 159 | SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = 'Ar2qeCzq6bLxnzZYn9MPFNle' 160 | 161 | SOCIAL_AUTH_URL_NAMESPACE = 'social' 162 | 163 | SOCIAL_AUTH_PIPELINE = ( 164 | 'social_core.pipeline.social_auth.social_details', 165 | 'social_core.pipeline.social_auth.social_uid', 166 | 'social_core.pipeline.social_auth.auth_allowed', 167 | 'social_core.pipeline.social_auth.social_user', 168 | 'social_core.pipeline.user.get_username', 169 | 'social_core.pipeline.user.create_user', 170 | 'social_core.pipeline.social_auth.associate_user', 171 | 'social_core.pipeline.social_auth.load_extra_data', 172 | 'social_core.pipeline.user.user_details', 173 | 174 | 'hiperion.pipeline.get_avatar', 175 | ) 176 | 177 | if not DEBUG: 178 | #Recomendaciones para produccion al ejecutar python manage.py check --deploy 179 | SECURE_BROWSER_XSS_FILTER = True #True => set the X-XSS-Protection: 1; mode=block header on all responses 180 | SECURE_CONTENT_TYPE_NOSNIFF = True #True => set the X-Content-Type-Options: nosniff header on all responses 181 | SECURE_SSL_REDIRECT = True #True => redirect all non-HTTPS requests to HTTPS 182 | SECURE_HSTS_SECONDS = 60 # non-zero => set the HTTP Strict Transport Security header on all responses 183 | SECURE_HSTS_INCLUDE_SUBDOMAINS = True #True => add the includeSubDomains tag to the HTTP Strict Transport Security header 184 | SECURE_HSTS_PRELOAD = True #True => adds the preload directive to the HTTP Strict Transport Security header 185 | SESSION_COOKIE_SECURE = True #True => the cookie will be marked as “secure”, which means browsers may ensure that the cookie is only sent under an HTTPS connection 186 | CSRF_COOKIE_SECURE = True #True => the cookie will be marked as “secure”, which means browsers may ensure that the cookie is only sent with an HTTPS connection 187 | X_FRAME_OPTIONS = 'DENY' #set the same X-Frame-Options value for all responses in your site 188 | 189 | 190 | #seguimiento a errores 191 | BUGSNAG = { 192 | 'api_key': BUGSNAG_API_KEY, 193 | 'project_root': os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 194 | } 195 | 196 | LOGGING = { 197 | 'version': 1, 198 | 'disable_existing_loggers': False, 199 | 200 | 'root': { 201 | 'level': 'ERROR', 202 | 'handlers': ['bugsnag'], 203 | }, 204 | 205 | 'handlers': { 206 | 'bugsnag': { 207 | 'level': 'INFO', 208 | 'class': 'bugsnag.handlers.BugsnagHandler', 209 | }, 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /voyager/sitemaps.py: -------------------------------------------------------------------------------- 1 | from django.contrib.sitemaps import Sitemap 2 | from hiperion.models import * 3 | 4 | """ 5 | class PostSitemap(Sitemap): 6 | 7 | def items(self): 8 | return Post.objects.all() 9 | 10 | class CategorySitemap(Sitemap): 11 | 12 | def items(self): 13 | return Category.objects.all() 14 | """ 15 | -------------------------------------------------------------------------------- /voyager/urls.py: -------------------------------------------------------------------------------- 1 | """voyager URL Configuration 2 | """ 3 | from django.contrib import admin 4 | from django.urls import path, include 5 | from django.contrib.auth.views import LoginView, LogoutView 6 | from django.conf import settings 7 | from django.conf.urls.static import static 8 | 9 | urlpatterns = [ 10 | path('admin/', admin.site.urls), 11 | 12 | path('login/', LoginView.as_view(), name='login'), 13 | path('logged-out/', LogoutView.as_view(), name='logout'), 14 | 15 | path('', include('social_django.urls', namespace='social')), 16 | 17 | path('', include('hiperion.urls')), 18 | path('', include('dione.urls')), 19 | ] 20 | 21 | #permite ver en desarrollo la foto colocando la url en el navegador ej. http://xxxx/media/photo/zakokor.jpg 22 | #en produccion los archivos los sirve NGINX 23 | if settings.DEBUG: 24 | urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) 25 | -------------------------------------------------------------------------------- /voyager/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for voyager 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/2.2/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', 'voyager.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /webpack-build-log.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zakokor/pegao/9e4d0a2c9dff74b808eefa05cfbe2b9bbe2058a2/webpack-build-log.json -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const glob = require('glob-all'); 3 | const webpack = require('webpack'); 4 | const TerserJSPlugin = require('terser-webpack-plugin'); 5 | const MiniCssExtractPlugin = require("mini-css-extract-plugin"); 6 | const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin'); 7 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; 8 | const PurgecssPlugin = require('purgecss-webpack-plugin') 9 | const PATHS = { 10 | src: path.join(__dirname, 'dione/src'), 11 | templates: path.join(__dirname, 'dione/templates'), 12 | base: path.join(__dirname, 'templates'), 13 | } 14 | 15 | module.exports = { 16 | optimization: { 17 | minimize: true, 18 | minimizer: [ 19 | new TerserJSPlugin({ 20 | terserOptions: { 21 | compress: { 22 | drop_console: true, 23 | }, 24 | } 25 | }), 26 | new OptimizeCSSAssetsPlugin({}) 27 | ], 28 | }, 29 | entry: { 30 | main: './dione/src/index.js', 31 | }, 32 | output: { 33 | path: path.resolve(__dirname, 'dione/static/dist'), 34 | filename: '[name].bundle.js', 35 | chunkFilename: '[id][hash].js' 36 | }, 37 | //devtool: "source-map", 38 | plugins: [ 39 | new MiniCssExtractPlugin({ 40 | filename: 'main.css', 41 | chunkFilename: '[id].css', 42 | }), 43 | //bundle analyzer on dev env 44 | /*new BundleAnalyzerPlugin({ 45 | analyzerMode: 'static' 46 | }),*/ 47 | //activate on production env 48 | new PurgecssPlugin({ 49 | paths: glob.sync([`${PATHS.src}/**/*`,`${PATHS.templates}/**/*`,`${PATHS.base}/**/*`], { nodir: true }), 50 | }), 51 | ], 52 | module: { 53 | rules: [ 54 | { 55 | test: /\.js$/, 56 | exclude: /node_modules/, 57 | use: { 58 | loader: "babel-loader", 59 | } 60 | }, 61 | { 62 | test: /\.scss$/, 63 | use: [ 64 | "style-loader", 65 | MiniCssExtractPlugin.loader, 66 | "css-loader", 67 | "sass-loader" 68 | ] 69 | }, 70 | ] 71 | }, 72 | }; --------------------------------------------------------------------------------