diff --git a/.gitignore b/.gitignore old mode 100755 new mode 100644 index 3efaa1b0..1e481675 --- a/.gitignore +++ b/.gitignore @@ -1,26 +1,28 @@ -# dependencies -/node_modules -/npm-debug.log* -/yarn-error.log -/yarn.lock +# Secrets +/**/**/.env +/**/**/origin.server +/**/**/server.manifest +/**/**/server.registry -# production -/build -/dist -/out +# Trash +/**/**/.crash.log +/**/**/.tmp +/**/**/.cache +/**/**/out +/**/**/.out +/**/**/dist +/**/**/node_modules +/**/**/corenode_modules +/**/**/.DS_Store +/**/**/package-lock.json +/**/**/yarn.lock +/**/**/.evite -# umi -/packages/**/src/.umi -/packages/**/src/.umi-production -/packages/**/src/.umi-test -/packages/**/.env.local +# Logs +/**/**/npm-debug.log* +/**/**/yarn-error.log +/**/**/dumps.log +/**/**/corenode.log -/packages/*/src/.umi -/packages/*/src/.umi-production -/packages/*/src/.umi-test -/packages/*/.env.local - -/.env.local - -/packages/*/node_modules -/packages/**/node_modules \ No newline at end of file +# Temporal configurations +/**/**/.aliaser \ No newline at end of file diff --git a/packages/app/src/App.jsx b/packages/app/src/App.jsx index d58b5517..407f07d8 100644 --- a/packages/app/src/App.jsx +++ b/packages/app/src/App.jsx @@ -145,9 +145,6 @@ class App { RenderError: (props) => { return }, - initialization: () => { - return

Initializing...

- } } state = { diff --git a/packages/app/src/extensions/api/index.js b/packages/app/src/extensions/api/index.js index 9f9f4dce..9d944142 100644 --- a/packages/app/src/extensions/api/index.js +++ b/packages/app/src/extensions/api/index.js @@ -1,7 +1,6 @@ import config from 'config' import { Bridge } from "linebridge/client" import { Session } from "models" -import io from "socket.io-client" export default { key: "apiBridge", @@ -10,15 +9,6 @@ export default { mutateContext: { async initializeDefaultBridge() { this.apiBridge = await this.createBridge() - this.ws = io("http://localhost:9001/main", { transports: ["websocket"] }) - - this.ws.on("connect", () => { - console.log(this.ws.id) - }) - - this.ws.on("connect_error", (...context) => { - console.log(...context) - }) window.app.apiBridge = this.apiBridge }, diff --git a/packages/app/src/pages/404.js b/packages/app/src/pages/404.js deleted file mode 100644 index a0c3ba3c..00000000 --- a/packages/app/src/pages/404.js +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react' -import styles from './404.less' - -const Error404 = () => ( -
- -

OBA BLYAT

-

- ERROR 404 -

-
-) - -export default Error404 diff --git a/packages/app/src/pages/404.less b/packages/app/src/pages/404.less deleted file mode 100644 index ac10f8cc..00000000 --- a/packages/app/src/pages/404.less +++ /dev/null @@ -1,55 +0,0 @@ -/* devanagari */ -@font-face { - font-family: 'Poppins'; - font-style: normal; - font-weight: 400; - font-display: swap; - src: local('Poppins Regular'), local('Poppins-Regular'), url(https://fonts.gstatic.com/s/poppins/v6/pxiEyp8kv8JHgFVrJJbecmNE.woff2) format('woff2'); - unicode-range: U+0900-097F, U+1CD0-1CF6, U+1CF8-1CF9, U+200C-200D, U+20A8, U+20B9, U+25CC, U+A830-A839, U+A8E0-A8FB; -} - -/* latin-ext */ -@font-face { - font-family: 'Poppins'; - font-style: normal; - font-weight: 400; - font-display: swap; - src: local('Poppins Regular'), local('Poppins-Regular'), url(https://fonts.gstatic.com/s/poppins/v6/pxiEyp8kv8JHgFVrJJnecmNE.woff2) format('woff2'); - unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; -} - -/* latin */ -@font-face { - font-family: 'Poppins'; - font-style: normal; - font-weight: 400; - font-display: swap; - src: local('Poppins Regular'), local('Poppins-Regular'), url(https://fonts.gstatic.com/s/poppins/v6/pxiEyp8kv8JHgFVrJJfecg.woff2) format('woff2'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; -} - -.error { - background-color: transparent; - color: black; - text-align: center; - position: absolute; - top: 30%; - margin-top: -50px; - left: 50%; - margin-left: -100px; - width: 200px; - - :global .anticon { - font-size: 48px; - margin-bottom: 16px; - } - - h1 { - font-family: 'Poppins', sans-serif; - - } - - p { - font-family: 'Poppins', sans-serif; - } -} \ No newline at end of file diff --git a/packages/app/src/pages/@/[user].js b/packages/app/src/pages/@/[user].js deleted file mode 100644 index 5713afdf..00000000 --- a/packages/app/src/pages/@/[user].js +++ /dev/null @@ -1,237 +0,0 @@ -import React from 'react' -import * as antd from 'antd' -import { verbosity, objectToArrayMap } from '@nodecorejs/utils' -import * as Icons from 'components/Icons' -import { connect } from 'umi' - -import { pathMatchRegexp, booleanFix } from 'core' -import HandleError from 'core/libs/errorhandler' -import GlobalBadges from 'schemas/badges_list.json' -import { Invalid, PostsFeed} from 'components' -import FollowButton from './components/follow' - -import styles from './index.less' - -export class UserLayout extends React.Component { - state = { - styleComponent: "UserLayout", - userString: pathMatchRegexp('/@/:id', location.pathname)[1], - layoutData: { - avatar: null, - cover: null, - about: null, - followed: null, - followers: null - } - } - - handleClickFollow(user_id) { - if (typeof (this.props.onFollow) !== "undefined") { - this.updateFollow(!booleanFix(this.state.layoutData.is_following)) - - this.props.onFollow(user_id, (callback) => { - this.updateFollow(callback) - }) - } - } - - updateFollow(to) { - let updated = this.state.layoutData - updated.is_following = to - this.setState({ layoutData: updated }) - } - - componentDidMount() { - const { layoutData } = this.props - if (layoutData) { - this.setState({ layoutData: { ...this.state.layoutData, ...layoutData } }) - } - } - - renderUserBadges() { - let { layoutData } = this.state - if (typeof(layoutData.user_tags) == "undefined") { - return null - } - let userTags = objectToArrayMap(layoutData.user_tags) - let renderTags = [] - - if (!userTags) { - return null - } - - try { - userTags = JSON.parse(userTags[0].value.badges) - } catch (error) { - console.log(error) - } - - if (!userTags) { - return null - } - - objectToArrayMap(GlobalBadges).forEach(e => { - if(userTags.includes(e.value.id)) { - renderTags.push(e.value) - } - }) - - try { - if (Array.isArray(userTags)) { - return renderTags.map((element) => { - return( - - - {element.title ?? "maybe"} - - - ) - }) - } - } catch (error) { - return null - } - return null - } - - render() { - const { styleComponent } = this.state - const toStyles = e => styles[`${styleComponent}_${e}`] - const { followers_count } = this.state.layoutData.details ?? {} - const isFollowed = booleanFix(this.state.layoutData.is_following) - - if (!this.state.layoutData) { - return null - } - return ( -
-
- -
-
- -
- -
-
-
- {/* {this.renderUserBadges()} */} -
- -

{this.state.userString}

-
- - -
- -
-
{ this.handleClickFollow(this.state.layoutData.user_id) }} followed={isFollowed} />
-
- -
- -
- -
-
- ) - } -} - -@connect(({ app }) => ({ app })) -export default class UserIndexer extends React.Component { - state = { - ErrorCode: null, - loading: true, - response: null, - layoutData: null - } - - promiseState = async state => new Promise(resolve => this.setState(state, resolve)); - - handleClickFollow(user_id, callback) { - if (this.props.app.session_valid) { - const requestCallback = (callbackResponse) => { - if (callbackResponse.code == 200) { - const response = callbackResponse.response - const result =( response.follow ?? false) == "followed" ? true : false - if (typeof(response) !== "undefined") { - return callback(result) - }else{ - return false - } - }else{ - return callback(null) - } - } - - this.props.dispatch({ - type: "socket/use", - scope: "users", - invoke: "actions", - query: { - payload: { - userToken: this.props.app.session_token, - action: "follow", - user_id, - }, - callback: requestCallback - } - }) - }else{ - verbosity.log(`Needs auth to dispatch 'actions' event`) - } - - } - - componentDidMount() { - const matchRegexp = pathMatchRegexp('/@/:id', location.pathname) - - if (matchRegexp && this.props.app.session_valid) { - this.props.dispatch({ - type: "user/get", - payload: { - from: "profileData", - username: matchRegexp[1] - }, - callback: (callbackResponse) => { - if (callbackResponse.code == 200) { - this.setState({ loading: false, layoutData: callbackResponse.response }) - } else { - this.setState({ ErrorCode: callbackResponse.code }) - return HandleError({ code: callbackResponse.code, msg: "no message provided" }) - } - - } - }) - } else { - this.setState({ ErrorCode: 140 }) - } - } - render() { - if (this.state.ErrorCode) { - return - } - if (this.state.loading) { - return
- } - return ( -
- {this.handleClickFollow(...context)}} layoutData={this.state.layoutData} /> - -
- - ) - } -} - diff --git a/packages/app/src/pages/@/components/badges/index.js b/packages/app/src/pages/@/components/badges/index.js deleted file mode 100644 index e69de29b..00000000 diff --git a/packages/app/src/pages/@/components/follow/index.js b/packages/app/src/pages/@/components/follow/index.js deleted file mode 100644 index e1733237..00000000 --- a/packages/app/src/pages/@/components/follow/index.js +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react' -import styles from './index.less' -import classnames from 'classnames' - -const FollowButton = (props) => { - return ( - - {props.followed ? 'Following' : 'Follow'} - - ) -} - -export default FollowButton diff --git a/packages/app/src/pages/@/components/follow/index.less b/packages/app/src/pages/@/components/follow/index.less deleted file mode 100644 index 3c3a5d7e..00000000 --- a/packages/app/src/pages/@/components/follow/index.less +++ /dev/null @@ -1,32 +0,0 @@ -.button { - user-select: none; - width: 100px; - height: 30px; - padding: 5px 15px; - text-decoration: none; - text-align: center; - vertical-align: middle; - letter-spacing: 1px; - - &:hover { - color: #7e7e7e; - } - - &.disabled{ - &:hover { - border: none; - content: ''; - color: white; - border-radius: 8px; - background: linear-gradient(120deg, #6559ae, #ff7159, #6559ae); - background-size: 400% 400%; - animation: gradient 15s ease-in-out infinite, border 1s forwards ease-in-out reverse; - } - } -} - -@keyframes gradient { - 0% { background-position: 14% 0%; } - 50% { background-position: 87% 100%; } - 100% { background-position: 14% 0%; } -} diff --git a/packages/app/src/pages/@/components/menu/index.js b/packages/app/src/pages/@/components/menu/index.js deleted file mode 100644 index 51144682..00000000 --- a/packages/app/src/pages/@/components/menu/index.js +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react' -import * as antd from 'antd' -import { MoreOutlined } from 'components/Icons' - -const moreMenu = ( - - __ - __set2 - -) - -const Menu = (props) => { - return ( - - - - ) -} - -export default Menu diff --git a/packages/app/src/pages/@/index.less b/packages/app/src/pages/@/index.less deleted file mode 100644 index 5a64cd09..00000000 --- a/packages/app/src/pages/@/index.less +++ /dev/null @@ -1,111 +0,0 @@ -.UserLayout_wrapper{ - display: flex; - height: 100%; - width: 100%; - - flex-direction: column; - align-items: center; - - margin: auto; - overflow: overlay !important; -} - -.UserLayout_header{ - font-family: "Poppins", sans-serif; - color: #242424; - - vertical-align: top; - display: flex; - padding: 20px; - - height: auto; - width: 100%; - - max-width: 600px; - max-height: 200px; - - background-color: #ffffff; - border-radius: 0 0 20px 20px; - transform: translate(0, -5px); -} - -.UserLayout_cover{ - display: flex; - align-content: center; - - max-height: 400px; - max-width: 600px; - - overflow: hidden; - border-radius: 15px 15px 0 0; - border-radius: 8px; - - img { - width: 100%; - margin: auto; - } -} - -.UserLayout_title{ - color: #2d2d2d; - font-weight: 500; - font-size: 20px; - line-height: 28px; - width: 100%; - height: 100%; - word-break: break-all; - - h1 { - margin: 0; - width: fit-content; - } - - span{ - word-break: break-all; - } -} - -.UserLayout_avatar { - transform: translate(-35px, -45px); - - &>span { - width: 100%; - height: 100%; - } - - &.mobile{ - transform: translate(0,-90px); - margin: auto; - } - - :global { - .ant-avatar { - box-shadow: 13px 13px 17px 4px rgba(69, 69, 69, 0.151); - border-radius: 7px; - height: 120px; - width: 120px; - - img { - height: 100%; - width: 100%; - } - } - } -} - -.UserLayout_options{ - display: flex; - flex-direction: column; - text-align: left; - - > div { - text-align: left; - margin-bottom: 15px; - } -} - - - -.UserLayout_content{ - -} \ No newline at end of file diff --git a/packages/app/src/pages/[indexer].js b/packages/app/src/pages/[indexer].js deleted file mode 100644 index 8996c437..00000000 --- a/packages/app/src/pages/[indexer].js +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react' -import Error404 from './404.js' - -export default class PageIndexer extends React.Component { - render() { - const { location } = this.props - - // By default return Error 404 - return ( -
- -
- ) - } -} \ No newline at end of file diff --git a/packages/app/src/pages/account/components/editor/index.jsx b/packages/app/src/pages/account/components/editor/index.jsx new file mode 100644 index 00000000..dda0195d --- /dev/null +++ b/packages/app/src/pages/account/components/editor/index.jsx @@ -0,0 +1,181 @@ +import React from "react" +import debounce from "lodash/debounce" +import { Icons } from "components/Icons" + +import classnames from "classnames" +import * as antd from "antd" + +import "./index.less" + +export const EditAccountField = ({ id, component, props, header, handleChange, delay, defaultValue, allowEmpty }) => { + const [currentValue, setCurrentValue] = React.useState(defaultValue) + const [emittedValue, setEmittedValue] = React.useState(null) + + const debouncedHandleChange = React.useCallback( + debounce((value) => handleChange({ id, value }), delay ?? 300), + [], + ) + + const handleDiscard = (event) => { + if (typeof event !== "undefined") { + event.target.blur() + } + + setCurrentValue(defaultValue) + handleChange({ id, value: defaultValue }) + } + + React.useEffect(() => { + debouncedHandleChange(currentValue) + }, [emittedValue]) + + const onChange = (event) => { + event.persist() + let { value } = event.target + + if (typeof value === "string") { + if (value.length === 0) { + // if is not allowed to be empty, discard modifications + if (!allowEmpty) { + return handleDiscard(event) + } + } + } + + setCurrentValue(value) + setEmittedValue(value) + } + + const handleKeyDown = (event) => { + if (event.keyCode === 27) { + // "escape" pressed, reset to default value + handleDiscard(event) + } + } + + window.app.eventBus.on("discardAllChanges", () => { + setCurrentValue(defaultValue) + }) + + const RenderComponent = component + return ( +
+ {header ? header : null} + +
+ ) +} + +export default class EditAccount extends React.Component { + state = { + values: this.props.user ?? {}, + changes: [], + loading: false, + } + + toogleLoading = (to) => { + this.setState({ loading: to ?? !this.state.loading }) + } + + onSaveDone = (error, data) => { + this.setState({ changes: [] }) + this.toogleLoading(false) + } + + onSave = () => { + this.props.onSave(this.state.changes, this.onSaveDone) + } + + discardAll = () => { + window.app.eventBus.emit("discardAllChanges") + this.setState({ changes: [] }) // clean changes after emit, cause controller wont handle changes + } + + handleChange = (event) => { + const { id, value } = event + let changes = [...this.state.changes] + + changes = changes.filter((change) => change.id !== id) + + if (this.state.values[id] !== value) { + // changes detected + changes.push({ id, value }) + } + + this.setState({ changes }) + } + + renderActions = () => { + return ( +
0 })}> +
+ {this.state.loading && } + {this.state.changes.length} Changes +
+
+ + Save + +
+
+ + Discard all + +
+
+ ) + } + + render() { + const { username, fullName, email } = this.state.values + + return ( +
+ {this.renderActions()} +
+
+

+ Account information +

+ + Username +
+ } + component={antd.Input} + props={{ placeholder: "Username", disabled: true }} + handleChange={this.handleChange} + /> + + Name +
+ } + component={antd.Input} + props={{ placeholder: "Your full name" }} + handleChange={this.handleChange} + /> + + Email +
+ } + component={antd.Input} + props={{ placeholder: "Your email address", type: "email" }} + handleChange={this.handleChange} + /> + + + + ) + } +} diff --git a/packages/app/src/pages/account/components/editor/index.less b/packages/app/src/pages/account/components/editor/index.less new file mode 100644 index 00000000..9f83c10c --- /dev/null +++ b/packages/app/src/pages/account/components/editor/index.less @@ -0,0 +1,60 @@ +.edit_account{ + overflow: hidden!important; + height: 100%; +} + +.edit_account_wrapper{ + overflow: scroll; + > div { + margin-bottom: 20px; + } +} + +.edit_account_actions { + position: absolute; + bottom: 0; + right: 0; + + flex-direction: row; + align-items: center; + justify-content: center; + + width: 100%; + padding: 20px 0 20px 0; + background-color: rgba(221, 221, 221, 0.5); + backdrop-filter: blur(2px); + border-radius: 18px 18px 0 0; + + transition: all 0.3s ease-in-out; + + display: none; + opacity: 0; + + &.show { + display: flex; + opacity: 1; + } + + > div { + margin-left: 20px; + } +} + +.edit_account_actions_indicator{ + position: fixed; + left: 0; + margin-left: 30px; +} + +.edit_account_category{ + > div { + margin-bottom: 20px; + margin-left: 20px; + } +} + +.edit_account_field { + input:not(:focus){ + color:rgba(105, 105, 105, 0.5) + } +} \ No newline at end of file diff --git a/packages/app/src/pages/account/components/index.js b/packages/app/src/pages/account/components/index.js new file mode 100644 index 00000000..e6d93aef --- /dev/null +++ b/packages/app/src/pages/account/components/index.js @@ -0,0 +1,3 @@ +export { default as AccountEditor } from './editor' +export { default as SessionsView } from './sessionsView' +export { default as StatisticsView } from './statisticsView' \ No newline at end of file diff --git a/packages/app/src/pages/account/components/sessionsView/index.jsx b/packages/app/src/pages/account/components/sessionsView/index.jsx new file mode 100644 index 00000000..8f3e6b20 --- /dev/null +++ b/packages/app/src/pages/account/components/sessionsView/index.jsx @@ -0,0 +1,37 @@ +import React from "react" +import * as antd from "antd" +import { Icons } from "components/Icons" +import { Sessions } from "components" + +export default class SessionsView extends React.Component { + signOutAll = () => { + antd.Modal.warning({ + title: "Caution", + content: "This action will cause all sessions to be closed, you will have to log in again.", + onOk: () => { + //this.setState({ sessions: null }) + window.app.eventBus.emit("destroyAllSessions") + }, + okCancel: true, + }) + } + + render() { + const { sessions, decodedToken } = this.props + + if (!sessions) { + return + } + + return ( +
+ + {sessions && ( + + Destroy all sessions + + )} +
+ ) + } +} \ No newline at end of file diff --git a/packages/app/src/pages/account/components/statisticsView/index.jsx b/packages/app/src/pages/account/components/statisticsView/index.jsx new file mode 100644 index 00000000..e8eb1114 --- /dev/null +++ b/packages/app/src/pages/account/components/statisticsView/index.jsx @@ -0,0 +1,17 @@ +import React from "react" + +export default class StatisticsView extends React.Component { + state = { + statistics: null + } + + componentDidMount = async () => { + + } + + render() { + return
+ +
+ } +} diff --git a/packages/app/src/pages/account/index.jsx b/packages/app/src/pages/account/index.jsx new file mode 100644 index 00000000..e55fd687 --- /dev/null +++ b/packages/app/src/pages/account/index.jsx @@ -0,0 +1,181 @@ +import React from "react" +import * as antd from "antd" +import { Icons } from "components/Icons" + +import { Roles } from "components" +import { AccountEditor, SessionsView, StatisticsView } from "./components" + +import { Session } from "models" + +import "./index.less" + +const api = window.app.apiBridge + +const SelfViewComponents = { + sessionsView: SessionsView, + statisticsView: StatisticsView, +} + +const SelfViewTabDecorators = { + sessionsView: ( +
+ Sessions +
+ ), + statisticsView: ( +
+ Statistics +
+ ), +} + +class SelfView extends React.Component { + renderComponents = () => { + const renderTagDecorator = (key) => { + if (typeof this.props.decorators[key] !== "undefined") { + return this.props.decorators[key] + } + return key + } + + return Object.keys(this.props.components).map((key, index) => { + const Component = this.props.components[key] + + return ( + +
+ +
+
+ ) + }) + } + + render() { + return ( + + {this.renderComponents()} + + ) + } +} + +export default class Account extends React.Component { + static bindApp = ["userController", "sessionController"] + + state = { + isSelf: false, + user: null, + sessions: null + } + + componentDidMount = async () => { + const token = Session.decodedToken + const location = window.app.history.location + const query = new URLSearchParams(location.search) + + const requestedUser = location.state?.username ?? query.get("username") ?? token?.username + let state = this.state + + if (requestedUser != null) { + if (token.username === requestedUser) { + state.isSelf = true + state.sessions = await this.props.contexts.app.sessionController.getAllSessions() + } + + state.user = await this.props.contexts.app.userController.getData({ username: requestedUser }) + } + + this.setState(state) + } + + handleUpdateUserData = async (changes, callback) => { + const update = {} + if (Array.isArray(changes)) { + changes.forEach((change) => { + update[change.id] = change.value + }) + } + + await api.put + .selfUser(update) + .then((data) => { + callback(false, data) + }) + .catch((err) => { + callback(true, err) + }) + + window.app.eventBus.emit("forceReloadUser") + } + + openUserEdit = () => { + window.app.DrawerController.open("editAccount", AccountEditor, { + props: { + keyboard: false, + width: "45%", + bodyStyle: { + overflow: "hidden", + }, + }, + componentProps: { + onSave: this.handleUpdateUserData, + user: this.state.user, + }, + }) + } + + renderSelfActions = () => { + if (this.state.isSelf) { + return ( +
+ Edit +
+ ) + } + + return null + } + + render() { + const user = this.state.user + + if (!user) { + return + } + + return ( +
+
+ +
+ {Boolean(user.fullName) ? + <> +

{user.fullName}

+ @{user.username}#{user._id} + : + <> +

@{user.username}

+ #{user._id} + + } +
+ + {this.state.isSelf && this.renderSelfActions()} +
+ + {this.state.isSelf && ( + + )} +
+ ) + } +} diff --git a/packages/app/src/pages/account/index.less b/packages/app/src/pages/account/index.less new file mode 100644 index 00000000..a35a0e88 --- /dev/null +++ b/packages/app/src/pages/account/index.less @@ -0,0 +1,30 @@ +.account_wrapper{ + > div { + margin-bottom: 10px; + } +} + +.account_card{ + display: flex; + font-family: 'Roboto Mono', monospace; + + align-items: center; + + padding: 12px; + color: #333; + border: 1px solid #33333396; + border-radius: 12px; + + img { + width: 70px; + height: 70px; + } + + h1 { + margin: 0; + font-size: 35px; + span { + font-size: 12px; + } + } +} \ No newline at end of file diff --git a/packages/app/src/pages/index.js b/packages/app/src/pages/index.js deleted file mode 100644 index 0377ce38..00000000 --- a/packages/app/src/pages/index.js +++ /dev/null @@ -1,3 +0,0 @@ -export default () => { - return
-} \ No newline at end of file diff --git a/packages/app/src/pages/login/guest.js b/packages/app/src/pages/login/guest.js deleted file mode 100644 index b57ca446..00000000 --- a/packages/app/src/pages/login/guest.js +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react' -import * as antd from 'antd' -import { connect } from 'umi' - -@connect(({ app }) => ({ app })) -export default class GuestSession extends React.PureComponent { - state = { - accept: false, - } - - render() { - return ( -
-
- -
this.setState({accept: e.target.checked})} /> You have read and accept the TOS
- { this.props.dispatch({ type: "guestLogin" }) }} > Continue -
-
- ) - } -} diff --git a/packages/app/src/pages/login/index.js b/packages/app/src/pages/login/index.js deleted file mode 100644 index 30b170c0..00000000 --- a/packages/app/src/pages/login/index.js +++ /dev/null @@ -1,132 +0,0 @@ -import React from 'react' -import { iatToString } from 'core' -import { router, ui } from 'core/libs' - -import styles from './index.less' -import classnames from 'classnames' - -import * as antd from 'antd' -import * as Icons from 'components/Icons' - -import RegistrationForm from './register.js' -import NormalLoginForm from './login.js' -import GuestSession from './guest.js' - -import config from 'config' -import { connect } from 'umi' - -const types = [ - { - id: "login", - key: 0, - renderText: `Sign in ${config.app.siteName}` - }, - { - id: "register", - key: 1, - renderText: "Register" - }, - { - id: "guest", - key: 2, - renderText: "Use guest session" - }, - { - id: "forgot", - key: 3, - renderText: "Forgotten password" - }, -] - -const typesRenderMap = { - 0: , - 1: , - 2: -} - -@connect(({ app }) => ({ app })) -class Login extends React.Component { - state = { - transition: false, - key: 0, - } - - renderHelperButtons() { - return types.map((e) => { - return ( - this.setState({ key: e.key })}> - {e.renderText || "Invalid"} - - ) - }) - } - - componentDidMount() { - if (this.props.app.session_valid) { - ui.notify.info('You have already logged into an account, you can change your account by logging in again') - } - } - - componentWillUnmount() { - antd.Modal.destroyAll() - } - - renderAccountModal() { - const dispatchLogout = () => this.props.dispatch({ type: "app/logout" }) - antd.Modal.confirm({ - title: this.props.app.session_data.username, - icon: , - onOk() { - router.push('/') - }, - onCancel() { - dispatchLogout() - }, - okText: <>Resume, - cancelText: <>Logout - }) - } - - renderAccountCard() { - if (this.props.app.session_authframe) { - return ( -
-
this.renderAccountModal()}> -

@{this.props.app.session_data.username}

-
Last login {iatToString(this.props.app.session_authframe.iat || 0)}
-
-
- ) - } - return null - } - - renderTitle() { - return ( -
-
YulioID™
-

{types[this.state.key].renderText || "Auth"}

-
- ) - } - - render() { - return ( -
-
-
- {this.renderTitle()} -
-
- {typesRenderMap[this.state.key]} -
- {this.renderHelperButtons()} -
-
- {this.renderAccountCard()} -
-
- ) - } -} -export default Login diff --git a/packages/app/src/pages/login/index.jsx b/packages/app/src/pages/login/index.jsx new file mode 100644 index 00000000..7a0f3587 --- /dev/null +++ b/packages/app/src/pages/login/index.jsx @@ -0,0 +1,145 @@ +import React from 'react' +import config from "config" +import * as antd from "antd" +import { FormGenerator } from 'components' +import { Icons } from 'components/Icons' + +import "./index.less" + +const formInstance = [ + { + id: "username", + element: { + component: "Input", + icon: "User", + placeholder: "Username", + props: null + }, + item: { + hasFeedback: true, + rules: [ + { + required: true, + message: 'Please input your Username!', + }, + ], + props: null + } + }, + { + id: "password", + element: { + component: "Input", + icon: "Lock", + placeholder: "Password", + props: { + type: "password" + } + }, + item: { + hasFeedback: true, + rules: [ + { + required: true, + message: 'Please input your Password!', + }, + ], + } + }, + { + id: "login_btn", + withValidation: true, + element: { + component: "Button", + props: { + icon: "User", + children: "Login", + type: "primary", + htmlType: "submit" + } + } + }, + { + id: "allowRegenerate", + withValidation: false, + element: { + component: "Checkbox", + props: { + children: "Not expire", + defaultChecked: false, + } + } + } +] + +export default class Login extends React.Component { + static bindApp = ["sessionController"] + + handleFinish = async (values, ctx) => { + ctx.toogleValidation(true) + + const payload = { + username: values.username, + password: values.password, + allowRegenerate: values.allowRegenerate, + } + + this.props.contexts.app.sessionController.login(payload, (error, response) => { + ctx.toogleValidation(false) + ctx.clearErrors() + + if (error) { + ctx.shake("all") + return ctx.error("result", error) + } else { + if (response.status === 200) { + this.onDone() + } + } + }) + } + + onDone = () => { + if (typeof this.props.onDone === "function") { + this.props.onDone() + } + } + + componentWillUnmount() { + window.app.SidebarController.toogleVisible(true) + window.app.HeaderController.toogleVisible(true) + } + + componentDidMount() { + if (window.app.SidebarController.isVisible()) { + window.app.SidebarController.toogleVisible(false) + } + + if (window.app.HeaderController.isVisible()) { + window.app.HeaderController.toogleVisible(false) + } + } + + render() { + return ( +
+ {this.props.session?.valid &&
+

You already have a valid session.

+
+ @{this.props.session.username} +
+ window.app.setLocation(config.app?.mainPath ?? "/main")} >Go to main +
} +
+ +
+
+ ) + } +} \ No newline at end of file diff --git a/packages/app/src/pages/login/index.less b/packages/app/src/pages/login/index.less index b4c52642..4ae1a815 100644 --- a/packages/app/src/pages/login/index.less +++ b/packages/app/src/pages/login/index.less @@ -1,244 +1,46 @@ -@import '~theme/index.less'; +.app_login { + height: 100vh; + width: 100vw; -.login_wrapper{ - width: 100%; - height: 100%; - font-family: "Poppins", sans-serif!important; - h1,h2,h3,h4,h5,h6{color: #333;} - position: absolute; - top: 0; - left: 0; - display: flex; - display: flex; - align-items: center; - justify-content: center; - font-size: 20px; - margin: auto; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; - overflow-y: scroll; - overflow-x: hidden; - &.goOut{ - .auth_box{ - -webkit-animation-name: fadeOutLeft; - animation-name: fadeOutLeft; - } - } - transition: all 300ms ease-in-out; + > div { + margin-bottom: 20px; + } } - -.auth_box{ - display: flex; - transition: all 300ms ease-in-out; - - .yid_logo { - vertical-align: middle; - height: 17px; - } - :global{ - input:-webkit-autofill, - input:-webkit-autofill:hover, - input:-webkit-autofill:focus, - textarea:-webkit-autofill, - textarea:-webkit-autofill:hover, - textarea:-webkit-autofill:focus, - select:-webkit-autofill, - select:-webkit-autofill:hover, - select:-webkit-autofill:focus { - border: 0; - -webkit-text-fill-color: #333; - -webkit-box-shadow: 0 0 0 1000px #ffffff98 inset; - box-shadow: #fff 0 0 0 1000px inset; - transition: background-color 5000s ease-in-out 0s; - } - - .ant-input-affix-wrapper { - width: 100%; - height: 40px; - padding: 4px 11px; - color: #333; - font-size: 14px; - line-height: 1.5715; - background-color: #fff; //rgba(255, 255, 255, 0.596); - border: 1.5px #2F66DF solid; - border-radius: 7px; - } - } +.login-form { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; } -.centering_wrapper{ - width: 100%; - text-align: center; -} - -.left_body{ - z-index: 50; - transform: translate(12px,0); - float: left; - width: 30%; - color: #333; - background-color: #fff; - padding: 20px; - border-radius: 12px 0 0 12px; - box-shadow: 0 10px 20px 0 rgba(51,51,51,0.52); - transition: all 300ms ease-in-out; -} - -.right_body{ - z-index: 51; - float: right; - width: 70%; - max-height: -webkit-fill-available; - height: 150px; - padding: 20px 50px; - color: #333; - background-color: #fff; - border-radius: 12px; - box-shadow: 0 10px 20px 0 rgba(51,51,51,0.52); - transition: all 300ms ease-in-out; -} - -.third_body{ - z-index: 50; - transform: translate(0, -24px); - float: right; - width: 100%; - max-height: -webkit-fill-available; - height: 120px; - padding: 45px 50px; - color: #333; - background-color: #fff; - border-radius: 12px; - box-shadow: 0 10px 20px 0 rgba(51, 51, 51, 0.52); - transition: all 300ms ease-in-out; - - .last_auth{ - font-size: 14px; - - } -} - -.third_body:hover{ - background-color: rgba(255, 255, 255, 0.3); - box-shadow: 0 10px 20px 0 rgba(51, 51, 51, 0.8); - transform: translate(0, -16px); -} - -.helper_login_btn{ - transform: translate(-20px, -14px); -} - -.login_helper_footer{ - width: 100%; - position: absolute; - bottom: 0; - left: 0; - right: 0; - display: flex; - margin: auto; - :global{ - .ant-btn{ - margin: auto; - padding: 0 5px; - } - } - - transform: translate(0, -10px); - -} - - -@keyframes go-out { - 0% { - filter: blur(0) - } - - 100% { - filter: blur(15px) - } -} - - -// Medium format max-width: 830px -@media (min-width: 486px){ - .auth_box { - padding: 0 16px 40px; - width: 500px; - min-height: 500px; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - } - .left_body{ - width: 100%; - float: none; - border-radius: 12px 12px 0 0; - padding: 20px; - transform: translate(0, 22px); - } - .right_body{ - width: 100%; - height: 100%; - float: none; - padding: 20px 60px; - } -} - -// Mobile format -@media (max-width: 485px){ - .auth_box { - position: absolute; - top: 0; - left: 0; - transition: all 150ms ease-in-out; - padding: 70px 16px 40px; - width: 100%; - height: 100%; - min-height: 500px; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - border-radius: 0; - } - .left_body{ - box-shadow: none; - width: 100%; - float: none; - position: absolute; - top: 0; - margin-top: 30px; - transform: translate(0, 0); - } - .right_body{ - box-shadow: none; - width: 100%; - float: none; - padding: 20px; - } -} - -@-webkit-keyframes fadeOutLeft { - from { - opacity: 1; - } - - to { - opacity: 0; - -webkit-transform: translate3d(-100%, 0, 0); - transform: translate3d(-100%, 0, 0); - } -} - -@keyframes fadeOutLeft { - from { - opacity: 1; - } - - to { - opacity: 0; - -webkit-transform: translate3d(-100%, 0, 0); - transform: translate3d(-100%, 0, 0); - } +.session_available { + width: fit-content; + height: fit-content; + + padding: 20px; + + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + + border: 1px solid #e5e5e5; + border-radius: 8px; + + .session_card { + width: fit-content; + height: fit-content; + + margin: 10px; + padding: 5px 10px; + + border: 1px solid #e5e5e5; + border-radius: 8px; + } } diff --git a/packages/app/src/pages/login/login.js b/packages/app/src/pages/login/login.js deleted file mode 100644 index 77951711..00000000 --- a/packages/app/src/pages/login/login.js +++ /dev/null @@ -1,229 +0,0 @@ -import React from 'react' -import styles from './index.less' - -import Fade from 'react-reveal/Fade' -import HeadShake from 'react-reveal/HeadShake'; - -import * as antd from 'antd' - -import { Form, Input, Button, Checkbox } from 'antd' -import { - UserOutlined, - LockOutlined, - BulbOutlined, - SwapLeftOutlined -} from 'components/Icons' -import { connect } from 'umi' - -@connect(({ app, socket }) => ({ app, socket })) -export default class NormalLoginForm extends React.Component { - state = { - usernameRaw: null, - passwordRaw: null, - - step: 1, - validating: false, - error_count: 0, - step_error: false, - step_show: true, - } - - next() { - let step = this.state.step - this.setState({ validating: true, step_error: false }) - switch (step) { - case 1: { - if (this.state.usernameRaw) { - const payload = { username: this.state.usernameRaw } - user.get.basicData(payload, (err, res) => { - if (res.code == 200) { - step++ - this.anim_transition(50) - return this.setState({ - validating: false, - early_data: res.response, - step: step, - }) - } - if (res.code == 210) { - return this.anim_error() - } - return false - }) - } else { - return this.anim_error() - } - } - case 2: { - return this.auth() - } - default: - return false - } - } - - back() { - if (this.state.step > 1) { - this.state.step-- - this.anim_transition(150) - } - this.setState({ step: this.state.step }) - } - - anim_transition(duration) { - this.setState({ step_show: false }) - setTimeout(() => { - this.setState({ step_show: true }) - }, duration || 150) - } - - anim_error() { - this.setState({ step_error: true, error_count: (this.state.error_count + 1), validating: false }) - } - - onChangeField(event) { - if(!this.state) { - return false - } - let updated = this.state - updated[event.target.id] = event.target.value - this.setState(updated) - } - - auth() { - const { usernameRaw, passwordRaw } = this.state - if (!usernameRaw || !passwordRaw) return false - this.setState({ step_error: false, validating: true }) - - this.props.dispatch({ - type: 'app/login', - payload: { username: usernameRaw, password: passwordRaw }, - callback: (callbackResponse) => { - console.log(callbackResponse) - this.setState({ validating: false }) - switch (callbackResponse) { - case 100: { - return null - } - case 400: { - console.log('Credentials error') - return this.anim_error() - } - case 500: { - console.log('Server error') - return this.back() - } - default: { - console.log('Unknown error') - return this.back() - } - } - } - }) - } - - renderFormItems = { - username: () => { - return ( - - this.next()} - id="usernameRaw" - onChange={(e) => this.onChangeField(e)} - prefix={} - placeholder="Username or Email" - /> - - ) - }, - password: () => { - return ( - <> -

Welcome Back @{this.state.early_data.username}

- - this.next()} - disabled={this.state.validating} - id="passwordRaw" - prefix={} - onChange={(e) => this.onChangeField(e)} - placeholder="Password" - /> - - - ) - } - } - - renderButtons() { - const PrimaryButton = () => { - return ( - - ) - } - - const SecondaryButton = () => { - return ( - - ) - } - if (this.state.step > 1) { - return
- } - return - } - - render() { - return ( -
- -
- - {this.renderFormItems[this.state.step == 1 ? "username" : "password"]()} - -
-
- {this.renderButtons()} -
- ) - } -} diff --git a/packages/app/src/pages/login/register.js b/packages/app/src/pages/login/register.js deleted file mode 100644 index 2c48a4ac..00000000 --- a/packages/app/src/pages/login/register.js +++ /dev/null @@ -1,151 +0,0 @@ -import React from 'react' -import * as Icons from 'components/Icons' -import styles from './index.less' - -import { - Form, - Input, - Tooltip, - Cascader, - Select, - Row, - Col, - Checkbox, - Button, - AutoComplete, -} from 'antd' - -import ReCAPTCHA from 'react-google-recaptcha' -import { g_recaptcha_key } from 'config/keys' - -export default class RegistrationForm extends React.Component { - state = { - usernameRaw: null, - passwordRaw: null, - emailRaw: null, - - captchaValue: null - } - - handleRegister() { - - } - - onFinish(values) { - console.log('Received values of form: ', values) - } - - onCaptcha(values) { - this.setState({ captchaValue: values }) - } - - onChangeField(event) { - if(!this.state) { - return false - } - let updated = this.state - updated[event.target.id] = event.target.value - this.setState(updated) - } - - renderForm() { - return ( -
- - this.onChangeField(e)} placeholder="randomuser" prefix={} /> - - - this.onChangeField(e)} placeholder="example@no-real.com" prefix={} /> - - - - this.onChangeField(e)} placeholder="mysupersecretpassword" prefix={} /> - - - - - - - - - - - - - - - value - ? Promise.resolve() - : Promise.reject('Should accept agreement'), - }, - ]} - > - - I have read the agreement - - - - - -
- ) - } - - render() { - return ( -
- { this.renderForm()} -
- ) - } - -} diff --git a/packages/app/src/pages/logout.js b/packages/app/src/pages/logout.js deleted file mode 100644 index 8e29686f..00000000 --- a/packages/app/src/pages/logout.js +++ /dev/null @@ -1,37 +0,0 @@ -import React from 'react' -import * as antd from 'antd' -import { connect } from 'umi' -import { router } from 'core/libs'; -import { Home, Trash } from 'components/Icons' -@connect(({ app }) => ({ app })) -export default class Logout extends React.Component{ - - componentDidMount(){ - if (!this.props.app.session_valid) { - return false - } - const dispatchLogout = () => this.props.dispatch({ type: "app/logout" }) - - antd.Modal.confirm({ - title: this.props.app.session_data.username, - icon: , - content: 'Are you sure you want to log out', - onOk() { - router.push('/') - }, - onCancel() { - dispatchLogout() - }, - okText: <>Resume, - cancelText: <>Logout - }); - } - - componentWillUnmount(){ - antd.Modal.destroyAll() - } - - render(){ - return null - } -} \ No newline at end of file diff --git a/packages/app/src/pages/main/index.jsx b/packages/app/src/pages/main/index.jsx new file mode 100644 index 00000000..ac3d2fef --- /dev/null +++ b/packages/app/src/pages/main/index.jsx @@ -0,0 +1,86 @@ +import React from "react" +import * as antd from "antd" +import { AppSearcher } from "components" +import * as charts from "react-chartjs-2" + +import "./index.less" + +const data = { + labels: ["Red", "Orange", "Blue"], + datasets: [ + { + label: "Popularity of colours", + data: [55, 23, 96], + borderWidth: 5, + fill: false, + }, + ], +} + +const chartKeys = Object.fromEntries(Object.keys(charts).map((key) => { + return [String(key).toLowerCase(), key] +})) + +class ChartGenerator extends React.Component { + constructor(payload) { + super(payload) + this.payload = payload + + this.type = this.payload.type + this.Chart = charts[this.type] ?? charts[chartKeys[this.type]] + + this.state = { + labels: [], + datasets: [], + } + + if (!this.Chart) { + console.error("Chart type is not valid") + } + } + + render() { + const { Chart } = this + + if (React.isValidElement(Chart)) { + return null + } + + return + } +} + +export default class Main extends React.Component { + componentWillUnmount() { + if (!window.app?.HeaderController?.isVisible()) { + window.app.HeaderController.toogleVisible(true) + } + } + + componentDidMount() { + if (window.app?.HeaderController?.isVisible()) { + window.app.HeaderController.toogleVisible(false) + } + } + + render() { + const user = this.props.user ?? {} + + return ( +
+
+
+

Welcome back, {user.fullName ?? user.username ?? "Guest"}

+
+
+ +
+
+
+

Statistics

+
+
+
+ ) + } +} diff --git a/packages/app/src/pages/main/index.less b/packages/app/src/pages/main/index.less new file mode 100644 index 00000000..5660d036 --- /dev/null +++ b/packages/app/src/pages/main/index.less @@ -0,0 +1,30 @@ +.dashboard { + padding-top: 20px; + width: 100%; + + h1{ + font-size: 28px; + margin: 0; + } + + .topΒ { + width: 100%; + display: flex; + align-items: center; + justify-content: space-between; + + > div { + margin-right: 20px; + } + } + + > div { + margin-bottom: 20px; + } + + .content { + > div { + margin-left: 20px; + } + } +} \ No newline at end of file diff --git a/packages/app/src/pages/new_streaming/index.js b/packages/app/src/pages/new_streaming/index.js deleted file mode 100644 index 2e9c56f9..00000000 --- a/packages/app/src/pages/new_streaming/index.js +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react' -import * as antd from 'antd' -import { connect } from 'umi' - -@connect(({ app }) => ({ app })) -export default class NewStreaming extends React.Component{ - render(){ - return( -
- -
- ) - } -} \ No newline at end of file diff --git a/packages/app/src/pages/post.js b/packages/app/src/pages/post.js deleted file mode 100644 index 46c6911f..00000000 --- a/packages/app/src/pages/post.js +++ /dev/null @@ -1,19 +0,0 @@ -import React from 'react' -import { connect } from 'umi' -import { PostsFeed } from 'components' - -@connect(({ app }) => ({ app })) -export default class Post extends React.Component{ - state = { - postID: null - } - componentDidMount(){ - this.setState({ postID: new URLSearchParams(location.search).get('key') }) - } - render(){ - if (!this.state.postID) { - return null - } - return - } -} \ No newline at end of file diff --git a/packages/app/src/pages/saves.js b/packages/app/src/pages/saves.js deleted file mode 100644 index fec5f5ac..00000000 --- a/packages/app/src/pages/saves.js +++ /dev/null @@ -1,10 +0,0 @@ -import React from 'react' -import { connect } from 'umi' -import { PostsFeed } from 'components' - -@connect(({ app }) => ({ app })) -export default class Saves extends React.Component{ - render(){ - return - } -} \ No newline at end of file diff --git a/packages/app/src/pages/settings/components/about/index.js b/packages/app/src/pages/settings/components/about/index.js deleted file mode 100644 index cb09617b..00000000 --- a/packages/app/src/pages/settings/components/about/index.js +++ /dev/null @@ -1,30 +0,0 @@ -import React from 'react' -import { About } from 'components' -import * as Icons from 'components/Icons' -import * as antd from 'antd' -import l from 'schemas/links' - -export default class AppAbout extends React.Component { - render() { - const handleClickLinks = (e) => { - const link = l[e] - link ? window.openLink(link) : console.log("Link not available") - } - return <> - - -
-

πŸŽ‰βœ¨ It's completely free and open source !

-
It is an impressive amount of work and effort, help us to continue offering quality services, you can support us from our patreon campaign.
- handleClickLinks("patreon")}> Support us with Patreon! -
- -
-

πŸ‘¨β€πŸ’» You are developer? You can help us by joining our team!

- handleClickLinks("github")}>Official Repository
- handleClickLinks("trello")}>Join our Trello -
-
- - } -} \ No newline at end of file diff --git a/packages/app/src/pages/settings/components/earnings/index.js b/packages/app/src/pages/settings/components/earnings/index.js deleted file mode 100644 index 997e5926..00000000 --- a/packages/app/src/pages/settings/components/earnings/index.js +++ /dev/null @@ -1,37 +0,0 @@ -import React from 'react' -import * as antd from 'antd' -import * as Icons from 'components/Icons' - - -export default class Earnings extends React.Component { - state = { - loading: true, - pro_points: 0 - } - componentDidMount(){ - // app.comty_data.get_user_data((err,res)=>{ - // if (err) return false - // try { - // const a = JSON.parse(res)['user_data'] - // this.setState({ loading: false, pro_points: a.points }) - // } catch (error) { - - // } - // }) - - } - render() { - return ( -
-
-

Redeem an Code

- -
-
-

Your Pro Points

- {this.state.pro_points} -
-
- ) - } -} diff --git a/packages/app/src/pages/settings/components/electron/index.js b/packages/app/src/pages/settings/components/electron/index.js deleted file mode 100644 index 8a1dd019..00000000 --- a/packages/app/src/pages/settings/components/electron/index.js +++ /dev/null @@ -1,91 +0,0 @@ -import React from 'react' -import * as antd from 'antd' -import * as Icons from 'components/Icons' -import styles from './index.less' -import { connect } from 'umi'; -import { package_json } from 'core' -import { objectToArrayMap } from '@nodecorejs/utils' - -const AppTech = (info) => { - if (!info) return null - return( -
-
umi{info.g_umi.version}
-
react{info.react_version}
-
{info.process.versions.v8}
-
{info.process.version}
-
{info.process.versions.electron}
-
Webpack
-
Socket.io
-
JS
-
TS
-
WebAssembly
-
OpenAI
-
- ) -} - - -@connect(({ app }) => ({ app })) -export default class ElectronSettings extends React.PureComponent{ - state = { - loading: true, - info: [] - } - - getInfo(){ - this.setState({ loading: true }) - this.setState({ - loading: false, - info: { - g_umi: window.g_umi, - process: window.process, - react_version: React.version, - deps: objectToArrayMap(package_json.dependencies) - } - }) - } - - componentDidMount(){ - this.getInfo() - } - - - render(){ - const showAppTech = () => { - antd.Modal.info({ - title: package_json.title, - content: AppTech(this.state.info), - width: 550 - }) - } - - const showThirdParty = () => { - const generateList = () => { - return this.state.info.deps.map((e) => { - return( -
- -> {e.key} {e.value.slice(1,e.value.length)} -
- ) - }) - } - - antd.Modal.info({ - title: package_json.title, - content: generateList(), - width: 550 - }) - } - - if (this.state.loading){ - return - } - return( -
- showAppTech()}> App Technologies - showThirdParty()}> Third-Party -
- ) - } -} \ No newline at end of file diff --git a/packages/app/src/pages/settings/components/electron/index.less b/packages/app/src/pages/settings/components/electron/index.less deleted file mode 100644 index 808ab280..00000000 --- a/packages/app/src/pages/settings/components/electron/index.less +++ /dev/null @@ -1,15 +0,0 @@ -.versions{ - overflow: scroll; - display: grid; - grid-template-columns: repeat(4, 1fr); - grid-template-rows: repeat(4, 1fr); - grid-column-gap: 15px; - grid-row-gap: 15px; - - > div { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - } -} \ No newline at end of file diff --git a/packages/app/src/pages/settings/components/general/index.js b/packages/app/src/pages/settings/components/general/index.js deleted file mode 100644 index 41b0cb59..00000000 --- a/packages/app/src/pages/settings/components/general/index.js +++ /dev/null @@ -1,83 +0,0 @@ -import React from 'react' -import { List, Button, Switch, Checkbox, InputNumber, Input } from 'antd' -import * as Icons from 'components/Icons' - -import { verbosity } from '@nodecorejs/utils' -import { settings, newSetting } from 'core/libs/settings' -import listSettings from 'schemas/settings_general.json' - -const AntdComponents = { Button, Switch, Checkbox, InputNumber, Input } -export default class GeneralSettings extends React.Component { - state = { - list: listSettings, - } - - renderSetting = (item) => { - if (!item.type || !item.id) { - verbosity.log("Invalid component >", item) - return null - } - if (typeof(AntdComponents[item.type]) == "undefined") { - verbosity.log(`Invalid component, '${item.type}' not exists >`, item) - return null - } - - let itemProps = { - onChange: (e) => this.onChange(item, e), - checked: settings.get(item.id) - } - - switch (item.type) { - case 'Switch': { - itemProps = { ...itemProps } // checkedChildren: "Enabled", unCheckedChildren: "Disabled" - break - } - default: - break - } - - return React.createElement(AntdComponents[item.type], itemProps) - } - - onChange(item, event) { - try { - let to = event - - verbosity.colors({ log: { textColor: "blue" } }).log(`Updating setting (${item.id}) > ${to}`) - settings.set(item.id, to) - - const updatedValues = this.state.list.map(element => - element.id === item.id ? Object.assign(element, { value: to }) : element - ) - this.setState({ list: updatedValues }) - } catch (err) { - console.log(err) - } - } - - renderIcon(icon, props) { - if (!Icons[icon]) { - verbosity.log(`${icon} not exist!`) - return null - } - return React.createElement(Icons[icon], props ?? null) - } - - render() { - return ( - ( - - {this.renderIcon(item.icon)}{item.title}} - description={item.description} - /> - {this.renderSetting(item)} - - )} - /> - ) - } -} \ No newline at end of file diff --git a/packages/app/src/pages/settings/components/help/index.js b/packages/app/src/pages/settings/components/help/index.js deleted file mode 100644 index 75efbdc3..00000000 --- a/packages/app/src/pages/settings/components/help/index.js +++ /dev/null @@ -1,9 +0,0 @@ -import React from 'react' - -export default class Help extends React.Component{ - render(){ - return <> - - - } -} \ No newline at end of file diff --git a/packages/app/src/pages/settings/components/keybinds/index.js b/packages/app/src/pages/settings/components/keybinds/index.js deleted file mode 100644 index 2864fd7b..00000000 --- a/packages/app/src/pages/settings/components/keybinds/index.js +++ /dev/null @@ -1,10 +0,0 @@ -import React from 'react' -import { connect } from 'umi' - -@connect(({ app }) => ({ app })) -export default class Keybinds extends React.Component { - render() { - return <> - } -} - diff --git a/packages/app/src/pages/settings/components/notification/index.js b/packages/app/src/pages/settings/components/notification/index.js deleted file mode 100644 index 2b712970..00000000 --- a/packages/app/src/pages/settings/components/notification/index.js +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react' -import * as Icons from 'components/Icons' -import styles from './index.less' - -export default class NotificationView extends React.Component { - render() { - return ( -
- -
- ) - } -} - diff --git a/packages/app/src/pages/settings/components/notification/index.less b/packages/app/src/pages/settings/components/notification/index.less deleted file mode 100644 index 4eab7076..00000000 --- a/packages/app/src/pages/settings/components/notification/index.less +++ /dev/null @@ -1,17 +0,0 @@ -@import '~theme/index.less'; - -.main { - - :global { - h2 { - font-weight: 500; - } - - .anticon { - color: #2d2d2d; - - } - - - } -} \ No newline at end of file diff --git a/packages/app/src/pages/settings/components/plugins/index.js b/packages/app/src/pages/settings/components/plugins/index.js deleted file mode 100644 index 4bb9481d..00000000 --- a/packages/app/src/pages/settings/components/plugins/index.js +++ /dev/null @@ -1,9 +0,0 @@ -import React from 'react' -import {connect} from 'umi' -@connect(({ app }) => ({ app })) -export default class Plugins extends React.Component { - render() { - return <> - - } -} \ No newline at end of file diff --git a/packages/app/src/pages/settings/components/security/index.js b/packages/app/src/pages/settings/components/security/index.js deleted file mode 100644 index ce0a704b..00000000 --- a/packages/app/src/pages/settings/components/security/index.js +++ /dev/null @@ -1,61 +0,0 @@ -import React from 'react' -import * as Icons from 'components/Icons' -import * as antd from 'antd' -import styles from './index.less' - -import Sessions_Manager from './sessions.js' - -const { Menu } = antd -export default class SecurityView extends React.Component { - state = { - current: 'privacy', - } - - handleClick = e => { - this.setState({ - current: e.key, - }) - } - - renderChildren = () => { - const { current } = this.state - switch (current) { - case 'privacy': - return null - case 'credentials': - return null - case 'sessions': - return - default: - break - } - return null - } - - render() { - return ( -
- - - - Privacy - - - - Credentials - - - - Sessions - - -
{this.renderChildren()}
-
- ) - } -} - diff --git a/packages/app/src/pages/settings/components/security/index.less b/packages/app/src/pages/settings/components/security/index.less deleted file mode 100644 index 4eab7076..00000000 --- a/packages/app/src/pages/settings/components/security/index.less +++ /dev/null @@ -1,17 +0,0 @@ -@import '~theme/index.less'; - -.main { - - :global { - h2 { - font-weight: 500; - } - - .anticon { - color: #2d2d2d; - - } - - - } -} \ No newline at end of file diff --git a/packages/app/src/pages/settings/components/security/sessions.js b/packages/app/src/pages/settings/components/security/sessions.js deleted file mode 100644 index 5d4bbe42..00000000 --- a/packages/app/src/pages/settings/components/security/sessions.js +++ /dev/null @@ -1,38 +0,0 @@ -import React from 'react' -import * as antd from 'antd' - -export default class Sessions_Manager extends React.Component { - state = { - sessions_data: '', - } - componentDidMount() { - // app.comty_data.session_id((err, res) => { - // this.setState({ sid: res }) - // }) - // app.comty_data.sessions((err, res) => { - // const a = JSON.parse(res)['data'] - // this.setState({ sessions_data: a }) - // }) - } - render() { - return ( -
- ( - - -

Session #{item.id}

- {this.state.sid == item.session_id ? 'This Session' : null} -
-

{item.platform}

-

{item.ip_address}

-

{item.time}

-
-
- )} - /> -
- ) - } -} diff --git a/packages/app/src/pages/settings/components/theme/components/background/index.js b/packages/app/src/pages/settings/components/theme/components/background/index.js deleted file mode 100644 index 076366b8..00000000 --- a/packages/app/src/pages/settings/components/theme/components/background/index.js +++ /dev/null @@ -1,158 +0,0 @@ -import React from 'react' -import * as Icons from 'components/Icons' -import * as antd from 'antd' -import { connect } from 'umi' -import styles from '../../index.less' - -import { theme, getOptimalOpacityFromIMG, get_style_rule_value } from 'core/libs/style' -import { urlToBase64, imageToBase64, arrayToObject } from 'core' -import ThemeConfigurator from '../../configurator' - -@connect(({ app }) => ({ app })) -export default class BackgroundImage extends ThemeConfigurator { - state = { - configKey: "backgroundImage", - model: { active: false, opacity: null, src: null }, - - textColor: this.rgbToScheme(getComputedStyle(document.getElementById("appWrapper")).color), - overlayColor: this.rgbToScheme(getComputedStyle(document.getElementById("appWrapper")).backgroundColor), - - processing: null, - fileURL: null, - customURL: '', - } - - handleFileUpload = info => { - if (info.file.status === 'uploading') { - return this.setState({ processing: false }) - } - if (info.file.status === 'done') { - this.setState({ processing: true }) - imageToBase64(info.file.originFileObj, fileURL => { - this.setState({ fileURL: fileURL }) - this.proccessBackground(fileURL) - }) - } - } - - handleCustomURL(url) { - this.setState({ processing: true, fileURL: url }) - urlToBase64(url, fileURL => { - this.proccessBackground(fileURL) - }) - } - - proccessBackground(data) { - getOptimalOpacityFromIMG({ textColor: this.state.textColor, overlayColor: this.state.overlayColor, img: data }, (res) => { - this.handleUpdate({ active: true, src: this.state.fileURL, opacity: res }) - }) - } - - schemeToRGB(values) { - const scheme = values ? values : { r: '0', g: '0', b: '0' } - const r = scheme.r || '0' - const g = scheme.g || '0' - const b = scheme.b || '0' - return `rgb(${r}, ${g}, ${b})` - } - - rgbToScheme(rgb) { - const values = rgb.replace(/[^\d,]/g, '').split(','); - return { r: values[0], g: values[1], b: values[2] } - } - - renderPreviewModel() { - return ( -
-

Preview

- { this.state.model.src ?
-
-

Sample text

-

Sample text

-

Sample text

-

Sample text

-

Some text here

-

Some text here

-
- -
:

No Background

} -
- ) - } - - renderUploader() { - return ( -
-

Upload

-
-
- Upload from your files
- - } /> - -
-
-

Or

-
-
- Upload from URL - this.handleCustomURL(this.state.customURL)} onChange={e => this.setState({ customURL: e.target.value })} value={this.state.customURL} placeholder="http://example.com/my_coolest_image.jpg" /> -
-
- - {this.state.processing ?

Processing image ...

: null} - {this.state.params ? JSON.stringify(this.state.params) : null} -
- ) - } - - renderControls() { - return ( -
-
-

Enabled

- { this.promiseState(prevState => ({ model: { ...prevState.model, active: e } })).then(() => this.handleUpdate()) }} - checked={this.state.model.active} - /> -
-
- -

Opacity

- { this.setState(prevState => ({ model: { ...prevState.model, opacity: e / 100 } })) }} onAfterChange={() => this.handleUpdate()} value={this.state.model.opacity * 100} /> -
-
-

Export Code

- this.handleExport()}> Export -
-
-

Import Code

- null}> Import -
-
-

Erase

- this.handleErase()} okText="Yes" cancelText="No"> - Delete - -
- -
- ) - } - - render() { - return ( -
- {this.renderControls()} - - {this.renderPreviewModel()} - - {this.renderUploader()} - -
- ) - } -} \ No newline at end of file diff --git a/packages/app/src/pages/settings/components/theme/components/color/index.js b/packages/app/src/pages/settings/components/theme/components/color/index.js deleted file mode 100644 index eeccc90e..00000000 --- a/packages/app/src/pages/settings/components/theme/components/color/index.js +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react' -import * as Icons from 'components/Icons' -import * as antd from 'antd' -import styles from '../../index.less' -import ThemeConfigurator from '../../configurator' - -export default class Colors extends ThemeConfigurator { - state = { - configKey: "colors", - model: { active: false } - } - render() { - return ( -
-
- - -
-
- ) - } -} diff --git a/packages/app/src/pages/settings/components/theme/components/darkmode/index.js b/packages/app/src/pages/settings/components/theme/components/darkmode/index.js deleted file mode 100644 index 07841be4..00000000 --- a/packages/app/src/pages/settings/components/theme/components/darkmode/index.js +++ /dev/null @@ -1,27 +0,0 @@ -import React from 'react' -import * as Icons from 'components/Icons' -import * as antd from 'antd' -import styles from '../../index.less' -import ThemeConfigurator from '../../configurator' - -export default class DarkMode extends ThemeConfigurator { - state = { - configKey: "darkmode", - model: { active: false } - } - render() { - return ( -
-
-

Enabled

- { this.promiseState(prevState => ({ model: { ...prevState.model, active: e } })).then(() => this.handleUpdate()) }} - checked={this.state.model.active} - /> -
-
- ) - } -} \ No newline at end of file diff --git a/packages/app/src/pages/settings/components/theme/configurator.js b/packages/app/src/pages/settings/components/theme/configurator.js deleted file mode 100644 index facf34eb..00000000 --- a/packages/app/src/pages/settings/components/theme/configurator.js +++ /dev/null @@ -1,50 +0,0 @@ -import React from 'react' -import ErrorHandler from 'core/libs/errorhandler' -import { theme } from 'core/libs/style' -import exportDataAsFile from 'core/libs/ui/export_data' -import { verbosity } from '@nodecorejs/utils' - -export default class ThemeConfigurator extends React.Component { - componentDidMount() { - this.applyStoraged() - } - - applyStoraged() { - const storaged = theme.get() - if (storaged && this.state) { - if (storaged[this.state.configKey]) { - return this.setState({ model: storaged[this.state.configKey] }) - } else { - return verbosity.log(`cannot get storagedSetting for ${this.state.configKey}`) - } - } - } - - promiseState = async state => new Promise(resolve => this.setState(state, resolve)); - - handleUpdate(payload) { - if (!this.state.configKey) { - return ErrorHandler({ msg: `cannot update without 'configKey', is missing`, code: 140 }) - } - if (!payload) { - payload = this.state.model - } - this.setState({ model: payload, processing: false }) - window.dispatcher({ - type: 'app/updateTheme', - payload: { - key: this.state.configKey, - value: payload - } - }); - } - - handleErase() { - this.handleUpdate({}) - } - - handleExport() { - exportDataAsFile({ data: JSON.stringify(this.state.model), type: 'text/json' }) - } - -} diff --git a/packages/app/src/pages/settings/components/theme/index.js b/packages/app/src/pages/settings/components/theme/index.js deleted file mode 100644 index f969f7be..00000000 --- a/packages/app/src/pages/settings/components/theme/index.js +++ /dev/null @@ -1,75 +0,0 @@ -import React from 'react' -import * as Icons from 'components/Icons' -import * as antd from 'antd' -import { connect } from 'umi' -import { arrayToObject } from 'core' -import ThemeSettingsList from 'schemas/theme_settings.json' - -import BackgroundSetting from './components/background' -import DarkmodeSetting from './components/darkmode' -import ColorSetting from './components/color' - -const componentsMap = { - backgroundImage: , - darkmode: , - color: , -} - -@connect(({ app }) => ({ app })) -export default class ThemeSettings extends React.Component { - state = { - selectedKey: null, - keys: [] - } - - componentDidMount() { - let mix = [] - ThemeSettingsList.forEach(e => { - mix[e.id] = e - }) - this.setState({ keys: mix }) - } - - renderSelectedKey() { - const selectedKeyItem = this.state.keys[this.state.selectedKey] ?? { icon: null, title: null } - return ( - this.setState({ selectedKey: null })} - visible={this.state.selectedKey ? true : false} - > - -
-

{selectedKeyItem.icon} {selectedKeyItem.title}

-
- - {componentsMap[this.state.selectedKey]} -
-
- ) - } - - render() { - const handleClick = (key) => this.setState({ selectedKey: key }) - const isActive = (key) => { return key ? key.active : false } - return ( -
- ( -
- handleClick(item.id)}> -

{React.createElement(Icons[item.icon])}{item.title}
{isActive(arrayToObject(this.props.app.app_theme)[item.id]) ? "Enabled" : "Disabled"}

-

{item.description}

-
-
- )} - /> - {this.renderSelectedKey()} -
- ) - } -} \ No newline at end of file diff --git a/packages/app/src/pages/settings/components/theme/index.less b/packages/app/src/pages/settings/components/theme/index.less deleted file mode 100644 index 9c0a5ce2..00000000 --- a/packages/app/src/pages/settings/components/theme/index.less +++ /dev/null @@ -1,51 +0,0 @@ -.background_image_controls{ - width: 100%; - margin: auto; - display: flex; - flex-direction: row; - - > div { - width: 100%; - margin: 0 10px; - } -} - -.background_image_uploader{ - width: 100%; - margin: auto; - display: flex; - flex-direction: row; - text-align: center; - - > div { - width: 100%; - margin: 0 10px; - } -} - -.background_image_preview{ - position: relative; - height: 50%; - border-radius: 8px; - - img{ - z-index: 9; - width: 100%; - border-radius: 8px; - } - - .text_wrapper{ - position: absolute; - z-index: 10; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - height: 100%; - width: 100%; - - h1{ - font-size: 68px; - } - } -} \ No newline at end of file diff --git a/packages/app/src/pages/settings/index.js b/packages/app/src/pages/settings/index.js deleted file mode 100644 index 7e21ae50..00000000 --- a/packages/app/src/pages/settings/index.js +++ /dev/null @@ -1,81 +0,0 @@ -import React from 'react' -import * as Icons from 'components/Icons' - -import { ListedMenu } from 'components' - -import NotificationView from './components/notification' -import SecurityView from './components/security' -import Base from './components/general' -import AppAbout from './components/about' -import Theme from './components/theme' -import ElectronApp from './components/electron' -import Keybinds from './components/keybinds' -import Plugins from './components/plugins' - -const Settings = { - base: , - about: , - keybinds: , - theme: , - plugins: , - security: , - notification: , - app: -} - -const menuList = [ - { - key: "base", - title: "General", - icon: , - }, - { - key: "app", - title: "Application", - icon: , - require: "embedded" - }, - { - key: "keybinds", - title: "Keybinds", - icon: - }, - { - key: "theme", - title: "Customization", - icon: , - }, - { - key: "plugins", - title: "Plugins", - icon: , - }, - { - key: "security", - title: "Security & Privacity", - icon: , - }, - { - key: "notification", - title: "Notification", - icon: , - }, - { - key: "help", - title: "Help", - icon: , - }, - { - key: "about", - title: "About", - icon: , - } -] - -class GeneralSettings extends React.Component { - render() { - return } title="Settings" childrens={Settings} menuArray={menuList} /> - } -} - -export default GeneralSettings diff --git a/packages/app/src/pages/streams/index.js b/packages/app/src/pages/streams/index.js deleted file mode 100644 index 34bfc768..00000000 --- a/packages/app/src/pages/streams/index.js +++ /dev/null @@ -1,52 +0,0 @@ -import React from 'react' -import * as antd from 'antd' -import * as Icons from 'components/Icons' -import { connect } from 'umi' -import { verbosity } from '@nodecorejs/utils' - -import { goLive } from 'core/models/helpers' - -@connect(({ app }) => ({ app })) -export default class Streams extends React.Component { - - state = { - list: [] - } - - componentDidMount() { - try { - - } catch (error) { - verbosity.log(error) - } - } - - availableList = () => { - if (!Array.isArray(this.state.list)) { - return false - } - if (this.state.list.length == 0) { - return false - } - return true - } - - render() { - - if (!this.availableList()) { - return
- - {goLive()}} type="primary" > - Start Streaming - - -
- } - - return ( -
- -
- ) - } -} \ No newline at end of file diff --git a/packages/server/.gitignore b/packages/server/.gitignore new file mode 100644 index 00000000..37d7e734 --- /dev/null +++ b/packages/server/.gitignore @@ -0,0 +1,2 @@ +node_modules +.env diff --git a/packages/server/package.json b/packages/server/package.json index d08f670b..c8e0af94 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -7,14 +7,15 @@ }, "license": "MIT", "dependencies": { - "corenode": "^0.28.26", - "linebridge": "^0.8.4", - "mongoose": "^6.0.13", "axios": "^0.24.0", "bcrypt": "^5.0.1", "connect-mongo": "^4.6.0", + "corenode": "^0.28.26", + "dicebar_lib": "^1.0.1", "jsonwebtoken": "^8.5.1", + "linebridge": "^0.8.4", "moment": "^2.29.1", + "mongoose": "^6.0.13", "passport": "^0.5.0", "passport-jwt": "^4.0.0", "passport-local": "^1.0.0", diff --git a/packages/server/src/controllers/RolesController/index.js b/packages/server/src/controllers/RolesController/index.js new file mode 100644 index 00000000..6674c103 --- /dev/null +++ b/packages/server/src/controllers/RolesController/index.js @@ -0,0 +1,36 @@ +import { Role, User } from '../../models' +import { selectValues } from "../../lib" + +export const RolesController = { + get: selectValues(["user_id", "username"], async (req, res) => { + const { user_id, username } = req.selectedValues + + if (typeof user_id !== "undefined" || typeof username !== "undefined") { + const user = await User.findOne(req.selectedValues) + if (!user) { + return res.status(404).json({ error: "No user founded" }) + } + return res.json(user.roles) + } + + const roles = await Role.find({}) + + return res.json(roles) + }), + set: (req, res, next) => { + const { name, description } = req.body + Role.findOne({ name }).then((data) => { + if (data) { + return res.status(409).json("This role is already created") + } + let document = new Role({ + name, + description + }) + document.save() + return res.json(true) + }) + } +} + +export default RolesController \ No newline at end of file diff --git a/packages/server/src/controllers/SessionController/index.js b/packages/server/src/controllers/SessionController/index.js new file mode 100644 index 00000000..297e600b --- /dev/null +++ b/packages/server/src/controllers/SessionController/index.js @@ -0,0 +1,104 @@ +import { Session } from '../../models' +import jwt from 'jsonwebtoken' +import { Token } from '../../lib' + +export const SessionController = { + regenerate: async (req, res) => { + jwt.verify(req.jwtToken, req.jwtStrategy.secretOrKey, async (err, decoded) => { + if (err && !decoded?.allowRegenerate) { + return res.status(403).send("This token is invalid and is not allowed to be regenerated") + } + + const sessionToken = await Session.findOneAndDelete({ token: req.jwtToken, user_id: decoded.user_id }) + + if (sessionToken) { + delete decoded["iat"] + delete decoded["exp"] + delete decoded["date"] + + const token = await Token.signNew({ + ...decoded, + }, req.jwtStrategy) + + return res.json({ token }) + } + }) + }, + deleteAll: async (req, res) => { + const { user_id } = req.body + + if (typeof user_id === "undefined") { + return res.status(400).send("No user_id provided") + } + + const allSessions = await Session.deleteMany({ user_id }) + if (allSessions) { + return res.send("done") + } + + return res.status(404).send("not found") + }, + delete: async (req, res) => { + const { token, user_id } = req.body + + if (typeof user_id === "undefined") { + return res.status(400).send("No user_id provided") + } + if (typeof token === "undefined") { + return res.status(400).send("No token provided") + } + + const session = await Session.findOneAndDelete({ user_id, token }) + if (session) { + return res.send("done") + } + + return res.status(404).send("not found") + }, + validate: async (req, res) => { + const token = req.body.session + let result = { + expired: false, + valid: true + } + + await jwt.verify(token, req.jwtStrategy.secretOrKey, async (err, decoded) => { + if (err) { + result.valid = false + result.error = err.message + + if (err.message === "jwt expired") { + result.expired = true + } + return + } + + result = { ...result, ...decoded } + + const sessions = await Session.find({ user_id: result.user_id }) + const sessionsTokens = sessions.map((session) => { + if (session.user_id === result.user_id) { + return session.token + } + }) + + if (!sessionsTokens.includes(token)) { + result.valid = false + result.error = "Session token not found" + } else { + result.valid = true + } + }) + + return res.json(result) + }, + get: async (req, res) => { + // get current session _id + const { _id } = req.user + const sessions = await Session.find({ user_id: _id }, { token: 0 }) + + return res.json(sessions) + }, +} + +export default SessionController \ No newline at end of file diff --git a/packages/server/src/controllers/UserController/index.js b/packages/server/src/controllers/UserController/index.js new file mode 100644 index 00000000..841e76ba --- /dev/null +++ b/packages/server/src/controllers/UserController/index.js @@ -0,0 +1,209 @@ +import passport from 'passport' +import bcrypt from 'bcrypt' + +import { User } from '../../models' +import SessionController from '../SessionController' +import { Token, selectValues } from '../../lib' +import AvatarController from 'dicebar_lib' + +export const UserController = { + isAuth: (req, res) => { + return res.json(`You look nice today 😎`) + }, + getSelf: (req, res) => { + return res.json(req.user) + }, + get: selectValues(["_id", "username"], async (req, res) => { + const user = await User.find(req.selectedValues, { username: 1, fullName: 1, _id: 1, roles: 1, avatar: 1 }) + + if (!user) { + return res.status(404).json({ error: "User not exists" }) + } + + return res.json(user) + }), + getOne: selectValues(["_id", "username"], async (req, res) => { + const user = await User.findOne(req.selectedValues) + + if (!user) { + return res.status(404).json({ error: "User not exists" }) + } + + return res.json(user) + }), + register: (req, res, next) => { + User.findOne({ username: req.body.username }) + .then((data) => { + if (data) { + return res.status(409).json("Username is already exists") + } + + const avatar = AvatarController.generate({ seed: req.body.username, type: "initials" }) + const hash = bcrypt.hashSync(req.body.password, parseInt(process.env.BCRYPT_ROUNDS)) + + let document = new User({ + username: req.body.username, + fullName: req.body.fullName, + avatar: avatar.uri, + email: req.body.email, + roles: ["registered"], + password: hash + }) + + return document.save() + }) + .then(data => { + return res.send(data) + }) + .catch(err => { + return next(err) + }) + }, + denyRole: async (req, res) => { + // check if issuer user is admin + if (!req.isAdmin()) { + return res.status(403).send("You do not have administrator permission") + } + + let { user_id, username, roles } = req.body + const userQuery = { + username: username, + user_id: user_id, + } + + // parse requested roles + if (typeof roles === "string") { + roles = roles.split(",").map(role => role.trim()) + } else { + return res.send("No effect") + } + + // get current user roles + const user = await User.findOne({ ...userQuery }) + if (typeof user === "undefined") { + return res.status(404).send(`[${username}] User not found`) + } + + // query all roles mutation + let queryRoles = [] + if (Array.isArray(roles)) { + queryRoles = roles + } else if (typeof roles === 'string') { + queryRoles.push(roles) + } + + // mutate all roles + if (queryRoles.length > 0 && Array.isArray(user.roles)) { + queryRoles.forEach(role => { + user.roles = user.roles.filter(_role => _role !== role) + }) + } + + // update user roles + await user.save() + return res.send("done") + }, + grantRole: async (req, res) => { + // check if issuer user is admin + if (!req.isAdmin()) { + return res.status(403).send("You do not have administrator permission") + } + + let { user_id, username, roles } = req.body + const userQuery = { + username: username, + user_id: user_id, + } + + // parse requested roles + if (typeof roles === "string") { + roles = roles.split(",").map(role => role.trim()) + } else { + return res.send("No effect") + } + + // get current user roles + const user = await User.findOne({ ...userQuery }) + if (typeof user === "undefined") { + return res.status(404).send(`[${username}] User not found`) + } + + // query all roles mutation + let queryRoles = [] + if (Array.isArray(roles)) { + queryRoles = roles + } else if (typeof roles === 'string') { + queryRoles.push(roles) + } + + + // mutate all roles + if (queryRoles.length > 0 && Array.isArray(user.roles)) { + queryRoles.forEach(role => { + if (!user.roles.includes(role)) { + user.roles.push(role) + } + }) + } + + // update user roles + await user.save() + return res.send("done") + }, + updatePassword: async (req, res) => { + //TODO + }, + updateSelf: async (req, res) => { + Object.keys(req.body).forEach(key => { + req.user[key] = req.body[key] + }) + + User.findOneAndUpdate({ _id: req.user._id }, req.user) + .then(() => { + return res.send(req.user) + }) + .catch((err) => { + return res.send(500).send(err) + }) + }, + update: async (req, res) => { + // TODO + }, + login: async (req, res) => { + passport.authenticate("local", { session: false }, async (error, user, options) => { + if (error) { + return res.status(500).json(`Error validating user > ${error.message}`) + } + + if (!user) { + return res.status(401).json("Invalid credentials") + } + + const payload = { + user_id: user._id, + username: user.username, + email: user.email + } + + if (req.body.allowRegenerate) { + payload.allowRegenerate = true + } + + // generate token + const token = Token.signNew(payload, options) + + // send result + res.json({ token: token }) + })(req, res) + }, + logout: async (req, res, next) => { + req.body = { + user_id: req.decodedToken.user_id, + token: req.jwtToken + } + + return SessionController.delete(req, res, next) + }, +} + +export default UserController diff --git a/packages/server/src/controllers/index.js b/packages/server/src/controllers/index.js new file mode 100644 index 00000000..da7be5c9 --- /dev/null +++ b/packages/server/src/controllers/index.js @@ -0,0 +1,3 @@ +export { default as RolesController } from './RolesController' +export { default as SessionController } from './SessionController' +export { default as UserController } from './UserController' \ No newline at end of file diff --git a/packages/server/src/endpoints/index.js b/packages/server/src/endpoints/index.js new file mode 100644 index 00000000..88d3b973 --- /dev/null +++ b/packages/server/src/endpoints/index.js @@ -0,0 +1,109 @@ +module.exports = [ + { + route: "/regenerate", + method: "POST", + middleware: ["ensureAuthenticated", "useJwtStrategy"], + fn: "SessionController.regenerate" + }, + { + route: "/role", + method: 'PUT', + middleware: ["ensureAuthenticated", "roles"], + fn: "UserController.grantRole" + }, + { + route: "/role", + method: 'DELETE', + middleware: ["ensureAuthenticated", "roles"], + fn: "UserController.denyRole" + }, + { + route: "/roles", + method: "GET", + fn: "RolesController.get", + }, + { + route: "/session", + method: 'DELETE', + middleware: "ensureAuthenticated", + fn: "SessionController.delete", + }, + { + route: "/sessions", + method: 'DELETE', + middleware: "ensureAuthenticated", + fn: "SessionController.deleteAll", + }, + { + route: "/validate_session", + method: "POST", + middleware: "useJwtStrategy", + fn: "SessionController.validate", + }, + { + route: "/sessions", + method: "GET", + middleware: "ensureAuthenticated", + fn: "SessionController.get", + }, + { + route: "/has_permissions", + method: "POST", + middleware: [ + "ensureAuthenticated", + "hasPermissions" + ] + }, + { + route: "/self", + method: "GET", + middleware: "ensureAuthenticated", + fn: "UserController.getSelf", + }, + { + route: "/users", + method: "GET", + middleware: "ensureAuthenticated", + fn: "UserController.get", + }, + { + route: "/user", + method: "GET", + middleware: "ensureAuthenticated", + fn: "UserController.getOne", + }, + { + route: "/self_user", + method: "PUT", + middleware: "ensureAuthenticated", + fn: "UserController.updateSelf", + }, + { + route: "/user", + method: "PUT", + middleware: ["ensureAuthenticated", "privileged"], + fn: "UserController.update", + }, + { + route: "/login", + method: "POST", + fn: "UserController.login", + }, + { + route: "/logout", + method: "POST", + middleware: ["ensureAuthenticated"], + fn: "UserController.logout", + }, + { + route: "/register", + method: "POST", + fn: "UserController.register", + }, + { + route: "/is_auth", + method: "POST", + middleware: "ensureAuthenticated", + fn: "UserController.isAuth", + } +] \ No newline at end of file diff --git a/packages/server/src/index.js b/packages/server/src/index.js index 592de37c..e3f3535d 100644 --- a/packages/server/src/index.js +++ b/packages/server/src/index.js @@ -92,7 +92,7 @@ class Server { return new Promise((resolve, reject) => { try { console.log("🌐 Trying to connect to DB...") - mongoose.connect(this.getDBConnectionString(), { useNewUrlParser: true, useFindAndModify: false }) + mongoose.connect(this.getDBConnectionString(), { useNewUrlParser: true, useUnifiedTopology: true }) .then((res) => { return resolve(true) }) .catch((err) => { return reject(err) }) } catch (err) { diff --git a/packages/server/src/lib/index.js b/packages/server/src/lib/index.js new file mode 100644 index 00000000..d1e792da --- /dev/null +++ b/packages/server/src/lib/index.js @@ -0,0 +1,3 @@ +export * as Token from './token' +export { default as Schematized } from './schematized' +export { default as selectValues } from './selectValues' \ No newline at end of file diff --git a/packages/server/src/lib/schematized/index.js b/packages/server/src/lib/schematized/index.js new file mode 100644 index 00000000..27d70ceb --- /dev/null +++ b/packages/server/src/lib/schematized/index.js @@ -0,0 +1,20 @@ +export default (schema, fn) => { + return async (req, res, next) => { + const missingKeys = [] + const requiredKeys = Array.isArray(schema) ? schema : [] + + for await (let key of requiredKeys) { + if (typeof req.body[key] === "undefined") { + missingKeys.push(key) + } + } + + if (missingKeys.length > 0) { + return res.status(400).json({ error: `Missing required keys > ${missingKeys}` }) + } else { + if (typeof fn === "function") { + return await fn(req, res, next) + } + } + } +} \ No newline at end of file diff --git a/packages/server/src/lib/selectValues/index.js b/packages/server/src/lib/selectValues/index.js new file mode 100644 index 00000000..641cf3dc --- /dev/null +++ b/packages/server/src/lib/selectValues/index.js @@ -0,0 +1,27 @@ +export default (query = [], fn) => { + return async (req, res, next) => { + if (typeof fn === "function") { + const obj = {} + + if (!req.body) { + req.body = {} + } + if (!req.query) { + req.query = {} + } + + if (Array.isArray(query)) { + query.forEach(key => { + const value = req.body[key] ?? req.query[key] + if (typeof value !== "undefined") { + obj[key] = value + } + }) + } + + req.selectedValues = obj + + return await fn(req, res, next) + } + } +} \ No newline at end of file diff --git a/packages/server/src/lib/token/index.js b/packages/server/src/lib/token/index.js new file mode 100644 index 00000000..ee48fea7 --- /dev/null +++ b/packages/server/src/lib/token/index.js @@ -0,0 +1,29 @@ +import jwt from 'jsonwebtoken' +import { nanoid } from 'nanoid' +import { Session } from '../../models' + +export function signNew(payload, options) { + const data = { + uuid: nanoid(), + allowRegenerate: false, + ...payload + } + + const token = jwt.sign(data, options.secretOrKey, { + expiresIn: options.expiresIn ?? "1h", + algorithm: options.algorithm ?? "HS256" + }) + + let newSession = new Session({ + uuid: data.uuid, + user_id: data.user_id, + allowRegenerate: data.allowRegenerate, + token: token, + date: new Date().getTime(), + location: options.sessionLocationSign + }) + + newSession.save() + + return token +} \ No newline at end of file diff --git a/packages/server/src/middlewares/ensureAuthenticated/index.js b/packages/server/src/middlewares/ensureAuthenticated/index.js new file mode 100644 index 00000000..7fe37a09 --- /dev/null +++ b/packages/server/src/middlewares/ensureAuthenticated/index.js @@ -0,0 +1,40 @@ +import passport from 'passport' +import { Session } from '../../models' + +export default (req, res, next) => { + function unauthorized() { + console.log("Returning failed session") + return res.status(401).send({ error: 'Invalid session', }) + } + + const authHeader = req.headers?.authorization?.split(' ') + + if (authHeader && authHeader[0] === 'Bearer') { + const token = authHeader[1] + + passport.authenticate('jwt', { session: false }, async (err, user, decodedToken) => { + if (err) { + return res.status(500).send({ error: err.message }) + } + + if (!user) { + return res.status(404).send({ error: "No user data found" }) + } + + const sessions = await Session.find({ user_id: decodedToken.user_id }) + const sessionsTokens = sessions.map(session => session.token) + + if (!sessionsTokens.includes(token)) { + return unauthorized() + } + + req.user = user + req.jwtToken = token + req.decodedToken = decodedToken + + return next() + })(req, res, next) + } else { + return unauthorized() + } +} diff --git a/packages/server/src/middlewares/errorHandler/index.js b/packages/server/src/middlewares/errorHandler/index.js new file mode 100644 index 00000000..d8e53df2 --- /dev/null +++ b/packages/server/src/middlewares/errorHandler/index.js @@ -0,0 +1,5 @@ +export const errorHandler = (error, req, res, next) => { + res.json({ error: error.message }) +} + +export default errorHandler \ No newline at end of file diff --git a/packages/server/src/middlewares/hasPermissions/index.js b/packages/server/src/middlewares/hasPermissions/index.js new file mode 100644 index 00000000..2cc254a8 --- /dev/null +++ b/packages/server/src/middlewares/hasPermissions/index.js @@ -0,0 +1,30 @@ +export const hasPermissions = (req, res, next) => { + if (typeof (req.userData) == "undefined") { + return res.status(403).json(`User data is not available, please ensure if you are authenticated`) + } + + const { _id, username, roles } = req.userData + const { permissions } = req.body + + req.userPermissions = roles + + let check = [] + + if (Array.isArray(permissions)) { + check = permissions + } else { + check.push(permissions) + } + + if (check.length > 0) { + check.forEach((role) => { + if (!roles.includes(role)) { + return res.status(403).json(`${username} not have permissions ${permissions}`) + } + }) + } + + next() +} + +export default hasPermissions diff --git a/packages/server/src/middlewares/index.js b/packages/server/src/middlewares/index.js new file mode 100644 index 00000000..e38789c7 --- /dev/null +++ b/packages/server/src/middlewares/index.js @@ -0,0 +1,4 @@ +export { default as ensureAuthenticated } from './ensureAuthenticated' +export { default as errorHandler } from './errorHandler' +export { default as hasPermissions } from './hasPermissions' +export { default as roles } from './roles' \ No newline at end of file diff --git a/packages/server/src/middlewares/privileged/index.js b/packages/server/src/middlewares/privileged/index.js new file mode 100644 index 00000000..1dfc5e62 --- /dev/null +++ b/packages/server/src/middlewares/privileged/index.js @@ -0,0 +1,7 @@ +export default (req, res, next) => { + if (!req.user.roles.includes("admin")) { + return res.status(401).send({ error: "To make this request it is necessary to have administrator permissions" }) + } + + next() +} \ No newline at end of file diff --git a/packages/server/src/middlewares/roles/index.js b/packages/server/src/middlewares/roles/index.js new file mode 100644 index 00000000..9e1d58f4 --- /dev/null +++ b/packages/server/src/middlewares/roles/index.js @@ -0,0 +1,11 @@ +export default (req, res, next) => { + req.isAdmin = () => { + if (req.user.roles.includes("admin")) { + return true + } + + return false + } + + next() +} \ No newline at end of file diff --git a/packages/server/src/models/index.js b/packages/server/src/models/index.js new file mode 100644 index 00000000..832d8162 --- /dev/null +++ b/packages/server/src/models/index.js @@ -0,0 +1,28 @@ +import mongoose from 'mongoose' +import { Schema } from 'mongoose' + +function getSchemas() { + const obj = Object() + + const _schemas = require("../schemas") + Object.keys(_schemas).forEach(key => { + obj[key] = Schema(_schemas[key]) + }) + + return obj +} + +const schemas = getSchemas() + +export const FabricObject = mongoose.model('FabricObject', schemas.FabricObject, "fabricObjects") +export const Workload = mongoose.model('Workload', schemas.Workload, "workload") +export const Workshifts = mongoose.model('Workshifts', schemas.Workshift, "workshifts") +export const Role = mongoose.model('Role', schemas.Role, 'roles') +export const Vault = mongoose.model('Vault', schemas.VaultItem, "vault") +export const GeoRegion = mongoose.model('GeoRegion', schemas.Region, "regions") + +export const Contract = mongoose.model('Contract', schemas.Contract, "contracts") +export const User = mongoose.model('User', schemas.User, "accounts") +export const Session = mongoose.model('Session', schemas.Session, "sessions") +export const Workshift = mongoose.model("Workshift", schemas.Workshift, "workshifts") +export const Report = mongoose.model("Report", schemas.Report, "reports") \ No newline at end of file diff --git a/packages/server/src/schemas/index.js b/packages/server/src/schemas/index.js new file mode 100644 index 00000000..86928a02 --- /dev/null +++ b/packages/server/src/schemas/index.js @@ -0,0 +1,3 @@ +export { default as User } from './user' +export { default as Role } from './role' +export { default as Session } from './session' \ No newline at end of file diff --git a/packages/server/src/schemas/role/index.js b/packages/server/src/schemas/role/index.js new file mode 100644 index 00000000..6992f3d0 --- /dev/null +++ b/packages/server/src/schemas/role/index.js @@ -0,0 +1,5 @@ +export default { + name: String, + description: String, + apply: Object +} \ No newline at end of file diff --git a/packages/server/src/schemas/session/index.js b/packages/server/src/schemas/session/index.js new file mode 100644 index 00000000..654e1d77 --- /dev/null +++ b/packages/server/src/schemas/session/index.js @@ -0,0 +1,9 @@ +export default { + allowRegenerate: { type: Boolean, default: false }, + uuid: { type: String, required: true }, + token: { type: String, required: true }, + user_id: { type: String, required: true }, + date: { type: Number, default: 0 }, + location: { type: String, default: "Unknown" }, + geo: { type: String, default: "Unknown" }, +} \ No newline at end of file diff --git a/packages/server/src/schemas/user/index.js b/packages/server/src/schemas/user/index.js new file mode 100644 index 00000000..2ef04b1c --- /dev/null +++ b/packages/server/src/schemas/user/index.js @@ -0,0 +1,10 @@ +export default { + username: { type: String, required: true }, + password: { type: String, required: true, select: false }, + fullName: String, + avatar: { type: String }, + email: String, + roles: [], + legal_id: Object, + phone: Number, +} \ No newline at end of file