73 |
74 | {this.state.isLoading && !this.state.error &&
}
75 |
76 | {this.state.error &&
{this.state.errorMessage} }
77 |
78 | {!this.state.isLoading && !this.state.error && this.state.hasData &&
79 |
80 |
Top 30 interactions
81 |
82 |
83 |
84 | { this.toggle('1'); }}>
87 | Highlights
88 |
89 |
90 |
91 | { this.toggle('2'); }} >
94 | Likes
95 |
96 |
97 |
98 | { this.toggle('3'); }} >
101 | Comments
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 | }
130 |
131 |
;
132 |
133 | }
134 | }
--------------------------------------------------------------------------------
/backend/app/models/base-model.js:
--------------------------------------------------------------------------------
1 | const mongoDB = require('./mongodb.js');
2 |
3 | exports.get = (collection, criteria) => {
4 |
5 | return new Promise((resolve, reject) => {
6 |
7 | mongoDB.connectDB((err, connection) => {
8 |
9 | if (err) {
10 | return reject(err);
11 | }
12 |
13 | connection.collection(collection).find(criteria).toArray((err, item) => {
14 | if (err) {
15 | return reject(err);
16 | }
17 |
18 |
19 | resolve(item);
20 | });
21 |
22 | });
23 | });
24 | };
25 |
26 | exports.find = (collection, criteria, sortBy, limit) => {
27 |
28 | return new Promise((resolve, reject) => {
29 |
30 | mongoDB.connectDB((err, connection) => {
31 |
32 | if (err) {
33 | return reject(err);
34 | }
35 |
36 | connection.collection(collection).find(criteria).limit(limit).sort(sortBy).toArray((err, items) => {
37 | if (err) {
38 | return reject(err);
39 | }
40 |
41 | resolve(items);
42 | });
43 |
44 | });
45 | });
46 |
47 | };
48 |
49 | exports.save = (collection, entry) => {
50 |
51 | return new Promise((resolve, reject) => {
52 |
53 | mongoDB.connectDB((err, connection) => {
54 |
55 | if (err) {
56 | return reject(err);
57 | }
58 |
59 | connection.collection(collection).insertOne(entry, (err, item) => {
60 | if (err) {
61 | return reject(err);
62 | }
63 |
64 | resolve(item.ops[0]);
65 | });
66 | });
67 |
68 | });
69 |
70 | };
71 |
72 | exports.replace = (collection, query, entry) => {
73 |
74 | return new Promise((resolve, reject) => {
75 |
76 | mongoDB.connectDB((err, connection) => {
77 |
78 | if (err) {
79 | return reject(err);
80 | }
81 |
82 | connection.collection(collection).replaceOne(query, entry, {upsert: false, multiple: false}, (err, item) => {
83 | if (err) {
84 | return reject(err);
85 | }
86 |
87 | resolve(item);
88 | });
89 |
90 | });
91 | });
92 |
93 | };
94 |
95 |
96 | exports.update = (collection, query, update) => {
97 |
98 | return new Promise((resolve, reject) => {
99 |
100 | mongoDB.connectDB((err, connection) => {
101 |
102 | if (err) {
103 | return reject(err);
104 | }
105 |
106 | connection.collection(collection).updateMany(query, update, {upsert: false, multiple: true}, (err, item) => {
107 | if (err) {
108 | return reject(err);
109 | }
110 |
111 | resolve(item);
112 | });
113 |
114 | });
115 | });
116 |
117 | };
118 |
119 |
120 | exports.remove = (collection, criteria) => {
121 |
122 | return new Promise((resolve, reject) => {
123 |
124 | mongoDB.connectDB((err, connection) => {
125 |
126 | if (err) {
127 | return reject(err);
128 | }
129 |
130 | connection.collection(collection).deleteMany(criteria, { multiple: true }, (err, numRemoved) => {
131 | if (err) {
132 | return reject(err);
133 | }
134 |
135 | resolve(numRemoved);
136 | });
137 |
138 | });
139 | });
140 |
141 | };
142 |
143 | exports.count = (collection, criteria) => {
144 |
145 | return new Promise((resolve, reject) => {
146 |
147 | mongoDB.connectDB((err, connection) => {
148 |
149 | if (err) {
150 | return reject(err);
151 | }
152 |
153 | connection.collection(collection).countDocuments(criteria, (err, count) => {
154 | if (err) {
155 | return reject(err);
156 | }
157 |
158 | resolve(count);
159 | });
160 | });
161 | });
162 | };
163 |
164 | exports.aggregate = (collection, criteria) => {
165 |
166 | return new Promise((resolve, reject) => {
167 |
168 | mongoDB.connectDB((err, connection) => {
169 |
170 | if (err) {
171 | return reject(err);
172 | }
173 |
174 | connection.collection(collection).aggregate(criteria).toArray((err, items) => {
175 | if (err) {
176 | return reject(err);
177 | }
178 |
179 | resolve(items);
180 | });
181 | });
182 | });
183 | };
184 |
185 |
186 | exports.aggregateToStream = (collection, criteria, pageSize, pageNum) => {
187 |
188 | return new Promise((resolve, reject) => {
189 |
190 | mongoDB.connectDB((err, connection) => {
191 |
192 | if (err) {
193 | return reject(err);
194 | }
195 |
196 | let cursor;
197 | if (pageSize === undefined || pageNum === undefined){
198 | cursor = connection.collection(collection).aggregate(criteria).batchSize(100).stream();
199 | } else {
200 | let intPageSize = parseInt(pageSize, 10);
201 | let skip = intPageSize * (parseInt(pageNum, 10) - 1);
202 |
203 | cursor = connection.collection(collection).aggregate(criteria).skip(skip).limit(intPageSize).batchSize(intPageSize).stream();
204 | }
205 |
206 | resolve(cursor);
207 |
208 | });
209 | });
210 | };
--------------------------------------------------------------------------------
/client/src/components/Home.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { ListGroup } from 'reactstrap';
3 | import ProfileList from './ProfileList';
4 | import '../css/Home.scss';
5 | import { apiBackendUrl } from '../helpers/constants';
6 | import { IProfile } from '../interfaces/IProfile';
7 | import FormModal from './FormModal';
8 | import LoadingSpinner from './LoadingSpinner';
9 |
10 | export default class Home extends React.Component<{}> {
11 |
12 | public state = {
13 | profiles: [] as IProfile[],
14 | loading: true
15 | }
16 |
17 | saveProfile = (username: string) => {
18 |
19 | this.setState({ loading: true });
20 |
21 | const requestOptions = {
22 | method: 'POST',
23 | headers: {
24 | 'Accept': 'application/json',
25 | 'Content-Type': 'application/json'
26 | },
27 | body: JSON.stringify({ "username": username })
28 | };
29 |
30 | fetch(apiBackendUrl + "profile/save", requestOptions).then(response => {
31 | if (!response.ok) {
32 | response.json().then(function (err) {
33 | alert(err.message);
34 | });
35 | return;
36 | }
37 |
38 | this.listProfiles();
39 | });
40 | }
41 |
42 | removeProfile = (username: string) => {
43 |
44 | if (!window.confirm("Confirm this action?")) {
45 | return;
46 | }
47 |
48 | this.setState({ loading: true });
49 |
50 | const requestOptions = {
51 | method: 'DELETE'
52 | };
53 |
54 | fetch(apiBackendUrl + "profile/" + username, requestOptions).then(response => {
55 | if (!response.ok) {
56 | response.json().then(function (err) {
57 | alert(err.message);
58 | });
59 | return;
60 | }
61 |
62 | this.listProfiles();
63 | });
64 | }
65 |
66 | listProfiles = () => {
67 | fetch(`${apiBackendUrl}profile/listAll?_t=${new Date().getTime()}`)
68 | .then(response => response.json())
69 | .then(profiles => this.setState({ profiles, loading: false }))
70 | .catch(e => e);
71 | }
72 |
73 | openWebSocket = () => {
74 | if ("WebSocket" in window) {
75 |
76 | let protocol = window["location"]["protocol"];
77 | let address = apiBackendUrl.substring(apiBackendUrl.indexOf(":") + 3);
78 | address = (((protocol + "").toLowerCase().indexOf("https") === 0) ? "wss://" : "ws://") + address;
79 |
80 | let wsSocket = new WebSocket(address);
81 |
82 | wsSocket.onopen = () => {
83 | console.log("Websocket connected!");
84 | };
85 |
86 | wsSocket.onmessage = (event) => {
87 | try {
88 |
89 | var jsonMessage = JSON.parse(event.data);
90 |
91 | if (jsonMessage.message) {
92 | if (jsonMessage.message === "new profile" || jsonMessage.message === "removed profile") {
93 | this.listProfiles();
94 | }
95 | }
96 |
97 | console.log(event.data);
98 |
99 | } catch (error) {
100 | //nothing
101 | }
102 | };
103 |
104 | wsSocket.onclose = () => {
105 | console.log("Websocket closed!");
106 | // Try to reconnect in 5 second
107 | setTimeout(this.openWebSocket, 5000);
108 | };
109 | }
110 | }
111 |
112 | componentDidMount() {
113 | console.log(apiBackendUrl);
114 | this.listProfiles();
115 | this.openWebSocket();
116 | setInterval(() => this.listProfiles(), 5000);
117 | }
118 |
119 | shouldComponentUpdate(nextProps: any, nextState: any) {
120 | return JSON.stringify(this.state.profiles) !== JSON.stringify(nextState.profiles) || this.state.loading !== nextState.loading;
121 | }
122 |
123 | render() {
124 | const list = this.state.profiles.map((profile) => {
125 | return
134 |
135 | {this.state.loading &&
}
136 |
137 | {!this.state.loading &&
{list} }
138 |
139 | {!this.state.loading &&
}
140 |
141 | {!this.state.loading &&
}
148 |
149 |
150 |
);
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/backend/app/public/static/css/main.49f7432d.chunk.css.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":["C:\\Users\\Ivan Valadares\\Desktop\\iscrapper-react/src\\css\\ProfileList.scss","C:\\Users\\Ivan Valadares\\Desktop\\iscrapper-react/src\\css\\Percentage.scss","C:\\Users\\Ivan Valadares\\Desktop\\iscrapper-react/src\\css\\LoadingBar.scss","C:\\Users\\Ivan Valadares\\Desktop\\iscrapper-react/src\\css\\PostStatsList.scss","C:\\Users\\Ivan Valadares\\Desktop\\iscrapper-react/src\\css\\PostStats.scss","C:\\Users\\Ivan Valadares\\Desktop\\iscrapper-react/src\\css\\Home.scss","C:\\Users\\Ivan Valadares\\Desktop\\iscrapper-react/src\\css\\FormModal.scss","C:\\Users\\Ivan Valadares\\Desktop\\iscrapper-react/src\\css\\LoadingSpinner.scss"],"names":[],"mappings":"AAAA,iBACI,YAAa,CADjB,2BAIQ,yBAA6B,CAJrC,+BAQQ,cAAe,CACf,cAAe,CACf,sBAAuB,CACvB,eAAgB,CAXxB,qCAcY,cAAe,CACf,UAAW,CACX,aAAc,CACd,eACJ,CAlBR,wBAsBQ,yBACJ,CCvBJ,QACI,aAAkB,CAGtB,UACI,SAAU,CCLd,WACI,UAAW,CACX,UAAW,CACX,iBAAkB,CAClB,eAAgB,CAChB,qBAAsB,CAL1B,kBAQQ,aAAc,CACd,iBAAkB,CAClB,UAAW,CACX,WAAY,CACZ,WAAY,CACZ,UAAW,CACX,wBAAyB,CACzB,+CAAwC,CAAxC,uCAAwC,CAI9C,8BACI,GAAM,WAAY,CAAE,SAAU,CAC9B,IAAK,SAAU,CACf,IAAK,SAAU,CACf,IAAM,QAAS,CACf,IAAK,SAAU,CACf,GAAI,SAAU,CAAA,CANlB,sBACI,GAAM,WAAY,CAAE,SAAU,CAC9B,IAAK,SAAU,CACf,IAAK,SAAU,CACf,IAAM,QAAS,CACf,IAAK,SAAU,CACf,GAAI,SAAU,CAAA,CCzBpB,aACI,WAAY,CCDhB,UACI,cAAe,CCDnB,KACI,YAAa,CACb,sCACJ,CAEA,GACI,oCAAyC,CACzC,2BAA6B,CAC7B,wBAA0B,CCP9B,OACC,cAAc,CACd,UAAU,CACV,WAAW,CACX,WAAW,CACX,UAAU,CACV,qBAAqB,CACrB,UAAU,CACV,kBAAkB,CAClB,iBAAiB,CACd,2BAA4B,CAC5B,UAAY,CACZ,0CAAoC,CAApC,kCAAoC,CAZxC,aAeQ,SAAU,CAIlB,UACC,eAAe,CAGhB,yCACC,OACC,WAAW,CACX,UAAU,CACV,CC5BF,SACI,cAAe,CACf,KAAM,CACN,OAAQ,CACR,QAAS,CACT,MAAO,CACP,eAAgB,CANpB,iBASQ,QAAS,CACT,gBAAiB,CACjB,cAAe,CAEf,yBAA6C,CAA7C,yBAA6C,CAC7C,2CAAoC,CAApC,mCAAoC,CAd5C,wCAmBQ,iBAAkB,CAClB,SAAU,CACV,UAAW,CACX,aAAc,CACd,iBAAkB,CAClB,OAAQ,CACR,kBAAmB,CAGvB,wBACI,GACI,8BAAuB,CAAvB,sBAAuB,CAG3B,GACI,+BAAyB,CAAzB,uBAAyB,CAAA,CANjC,gBACI,GACI,8BAAuB,CAAvB,sBAAuB,CAG3B,GACI,+BAAyB,CAAzB,uBAAyB,CAAA","file":"main.49f7432d.chunk.css","sourcesContent":[".list-group-item {\r\n padding: 10px;\r\n\r\n .progress {\r\n margin-bottom: 0px !important;\r\n }\r\n \r\n .profile-name {\r\n font-size: 16pt;\r\n cursor: pointer;\r\n text-overflow: ellipsis;\r\n overflow: hidden;\r\n\r\n small {\r\n font-size: 10px;\r\n color: grey;\r\n display: block;\r\n margin-top: -5px\r\n }\r\n }\r\n \r\n button {\r\n margin-left: 5px !important\r\n }\r\n\r\n}",".percUp {\r\n color: forestgreen;\r\n}\r\n\r\n.percDown {\r\n color: red;\r\n}",".loaderBar {\r\n height: 4px;\r\n width: 100%;\r\n position: relative;\r\n overflow: hidden;\r\n background-color: #ddd;\r\n\r\n &:before{\r\n display: block;\r\n position: absolute;\r\n content: \"\";\r\n left: -200px;\r\n width: 200px;\r\n height: 4px;\r\n background-color: #2980b9;\r\n animation: loadingBar 2s linear infinite;\r\n }\r\n }\r\n\r\n @keyframes loadingBar {\r\n from {left: -200px; width: 30%;}\r\n 50% {width: 30%;}\r\n 70% {width: 70%;}\r\n 80% { left: 50%;}\r\n 95% {left: 120%;}\r\n to {left: 100%;}\r\n }",".postPathCol {\r\n width: 120px;\r\n}",".nav-link {\r\n cursor: pointer;\r\n}","body {\r\n padding: 10px;\r\n font-family: Arial, Helvetica, sans-serif\r\n}\r\n\r\nhr {\r\n border-top: 1px dotted #000000 !important;\r\n margin-bottom: 5px !important;\r\n margin-top: 5px !important;\r\n}\r\n","\r\n.float{\r\n\tposition:fixed;\r\n\twidth:60px;\r\n\theight:60px;\r\n\tbottom:40px;\r\n\tright:40px;\r\n\tbackground-color:#0C9;\r\n\tcolor:#FFF;\r\n\tborder-radius:50px;\r\n\ttext-align:center;\r\n box-shadow: 2px 2px 3px #999;\r\n opacity: 0.7;\r\n transition: opacity 0.2s ease-in-out;\r\n\r\n &:hover {\r\n opacity: 1;\r\n }\r\n}\r\n\r\n.my-float{\r\n\tmargin-top:22px;\r\n}\r\n\r\n@media only screen and (max-width: 600px) {\r\n\t.float{\r\n\t\tbottom:20px;\r\n\t\tright:20px;\r\n\t}\r\n }",".loading {\r\n position: fixed;\r\n top: 0;\r\n right: 0;\r\n bottom: 0;\r\n left: 0;\r\n background: #fff;\r\n \r\n .loader {\r\n left: 50%;\r\n margin-left: -4em;\r\n font-size: 10px;\r\n border: .8em solid rgba(218, 219, 223, 1);\r\n border-left: .8em solid rgba(58, 166, 165, 1);\r\n animation: spin 1.1s infinite linear;\r\n }\r\n\r\n .loader,\r\n .loader:after {\r\n border-radius: 50%;\r\n width: 8em;\r\n height: 8em;\r\n display: block;\r\n position: absolute;\r\n top: 50%;\r\n margin-top: -4.05em;\r\n }\r\n\r\n @keyframes spin {\r\n 0% {\r\n transform: rotate(0deg);\r\n }\r\n\r\n 100% {\r\n transform: rotate(360deg);\r\n }\r\n }\r\n}\r\n"]}
--------------------------------------------------------------------------------
/client/src/components/PostModal.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { Alert, Button, Modal, ModalHeader, ModalBody, ModalFooter, Label } from 'reactstrap';
3 | import { FaExternalLinkAlt } from 'react-icons/fa';
4 | import Highcharts from 'highcharts';
5 | import highchartsSeriesLabel from "highcharts/modules/series-label";
6 | import HighchartsReact from 'highcharts-react-official';
7 | import { apiBackendUrl } from '../helpers/constants';
8 | import { IPost } from '../interfaces/IPost';
9 | import LoadingBar from './LoadingBar';
10 |
11 |
12 | highchartsSeriesLabel(Highcharts);
13 |
14 | interface IProps {
15 | post: IPost
16 | }
17 |
18 | export default class PostModal extends React.Component