mirror of
https://github.com/filecoin-project/slate.git
synced 2024-11-22 21:45:56 +03:00
postgres: user accounts, migration scripts, api route rewrite, stopping point
This commit is contained in:
parent
ee9e8fd27d
commit
b6a8c880cd
@ -16,7 +16,10 @@ Working on Slate requires an internet connection because we are using a hosted P
|
||||
|
||||
### .env
|
||||
|
||||
You will need to create a file called `.env`. Never commit your credentials to the repository. **You don't need this file if you only work on the design system.**
|
||||
- We use a `dotenv` file to manage sensitive values and secrets.
|
||||
- You must create this file to work on the application.
|
||||
- You don't need to create a `.env` file if you're only working on the design system.
|
||||
- There will be no local data in the short term.
|
||||
|
||||
```
|
||||
POSTGRES_ADMIN_PASSWORD=XXX
|
||||
|
@ -8,25 +8,8 @@ const REQUEST_HEADERS = {
|
||||
"Content-Type": "application/json",
|
||||
};
|
||||
|
||||
const dev = process.env.NODE_ENV !== "production";
|
||||
|
||||
const SERVER_PATH = dev
|
||||
? "http://localhost:1337"
|
||||
: "https://filecoin.onrender.com";
|
||||
|
||||
export const rehydrateViewer = async () => {
|
||||
const options = {
|
||||
method: "POST",
|
||||
headers: REQUEST_HEADERS,
|
||||
credentials: "include",
|
||||
body: JSON.stringify({}),
|
||||
};
|
||||
|
||||
const response = await fetch(`${SERVER_PATH}/_/viewer`, options);
|
||||
const json = await response.json();
|
||||
|
||||
return json;
|
||||
};
|
||||
const dev = process.env.NODE_ENV !== "www";
|
||||
const SERVER_PATH = dev ? "http://localhost:1337" : "https://slate.host";
|
||||
|
||||
export const setDefaultConfig = async (data) => {
|
||||
const options = {
|
||||
@ -85,3 +68,58 @@ export const sendWalletAddressFilecoin = async (data) => {
|
||||
|
||||
return json;
|
||||
};
|
||||
|
||||
// NOTE(jim):
|
||||
// New WWW Requests.
|
||||
export const hydrateAuthenticatedUser = async (data) => {
|
||||
const options = {
|
||||
method: "POST",
|
||||
headers: REQUEST_HEADERS,
|
||||
credentials: "include",
|
||||
body: JSON.stringify({ data }),
|
||||
};
|
||||
|
||||
const response = await fetch(`${SERVER_PATH}/api/hydrate`, options);
|
||||
const json = await response.json();
|
||||
|
||||
return json;
|
||||
};
|
||||
|
||||
export const deleteUser = async (data) => {
|
||||
const options = {
|
||||
method: "DELETE",
|
||||
headers: REQUEST_HEADERS,
|
||||
credentials: "include",
|
||||
body: JSON.stringify({ data }),
|
||||
};
|
||||
|
||||
const response = await fetch(`${SERVER_PATH}/api/users/delete`, options);
|
||||
const json = await response.json();
|
||||
return json;
|
||||
};
|
||||
|
||||
export const createUser = async (data) => {
|
||||
const options = {
|
||||
method: "POST",
|
||||
headers: REQUEST_HEADERS,
|
||||
credentials: "include",
|
||||
body: JSON.stringify({ data }),
|
||||
};
|
||||
|
||||
const response = await fetch(`${SERVER_PATH}/api/users/create`, options);
|
||||
const json = await response.json();
|
||||
return json;
|
||||
};
|
||||
|
||||
export const health = async (data) => {
|
||||
const options = {
|
||||
method: "POST",
|
||||
headers: REQUEST_HEADERS,
|
||||
credentials: "include",
|
||||
body: JSON.stringify({ data: { success: true } }),
|
||||
};
|
||||
|
||||
const response = await fetch(`${SERVER_PATH}/api/_`, options);
|
||||
const json = await response.json();
|
||||
return json;
|
||||
};
|
||||
|
@ -1,8 +1,3 @@
|
||||
if (process.env.NODE_ENV !== "www") {
|
||||
console.log("[ prototype ] loading dotenv");
|
||||
require("dotenv").config();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
development: {
|
||||
client: "pg",
|
||||
|
6
node_common/data/index.js
Normal file
6
node_common/data/index.js
Normal file
@ -0,0 +1,6 @@
|
||||
import createUser from "~/node_common/data/methods/create-user";
|
||||
import updateUserById from "~/node_common/data/methods/update-user-by-id";
|
||||
import deleteUserByUsername from "~/node_common/data/methods/delete-user-by-username";
|
||||
import getUserByUsername from "~/node_common/data/methods/get-user-by-username";
|
||||
|
||||
export { createUser, updateUserById, deleteUserByUsername, getUserByUsername };
|
27
node_common/data/methods/create-user.js
Normal file
27
node_common/data/methods/create-user.js
Normal file
@ -0,0 +1,27 @@
|
||||
import { runQuery } from "~/node_common/data/utilities";
|
||||
|
||||
export default async ({ email, password, username, salt, data = {} }) => {
|
||||
return await runQuery({
|
||||
label: "CREATE_USER",
|
||||
queryFn: async (DB) => {
|
||||
const query = await DB.insert({
|
||||
email,
|
||||
password,
|
||||
salt,
|
||||
data,
|
||||
username,
|
||||
})
|
||||
.into("users")
|
||||
.returning("*");
|
||||
|
||||
const index = query ? query.pop() : null;
|
||||
return index;
|
||||
},
|
||||
errorFn: async (e) => {
|
||||
return {
|
||||
error: "CREATE_USER",
|
||||
source: e,
|
||||
};
|
||||
},
|
||||
});
|
||||
};
|
20
node_common/data/methods/delete-user-by-username.js
Normal file
20
node_common/data/methods/delete-user-by-username.js
Normal file
@ -0,0 +1,20 @@
|
||||
import { runQuery } from "~/node_common/data/utilities";
|
||||
|
||||
export default async ({ username }) => {
|
||||
return await runQuery({
|
||||
label: "DELETE_USER_BY_USERNAME",
|
||||
queryFn: async (DB) => {
|
||||
const data = await DB.from("users")
|
||||
.where({ username })
|
||||
.del();
|
||||
|
||||
return 1 === data;
|
||||
},
|
||||
errorFn: async (e) => {
|
||||
return {
|
||||
error: "DELETE_USER_BY_USERNAME",
|
||||
source: e,
|
||||
};
|
||||
},
|
||||
});
|
||||
};
|
29
node_common/data/methods/get-user-by-username.js
Normal file
29
node_common/data/methods/get-user-by-username.js
Normal file
@ -0,0 +1,29 @@
|
||||
import { runQuery } from "~/node_common/data/utilities";
|
||||
|
||||
export default async ({ username }) => {
|
||||
return await runQuery({
|
||||
label: "GET_USER_BY_USERNAME",
|
||||
queryFn: async (DB) => {
|
||||
const query = await DB.select("*")
|
||||
.from("users")
|
||||
.where({ username })
|
||||
.first();
|
||||
|
||||
if (!query || query.error) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (query.id) {
|
||||
return query;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
errorFn: async (e) => {
|
||||
return {
|
||||
error: "GET_USER_BY_USERNAME",
|
||||
source: e,
|
||||
};
|
||||
},
|
||||
});
|
||||
};
|
26
node_common/data/methods/update-user-by-id.js
Normal file
26
node_common/data/methods/update-user-by-id.js
Normal file
@ -0,0 +1,26 @@
|
||||
import { runQuery } from "~/node_common/data/utilities";
|
||||
|
||||
export default async ({ id, data }) => {
|
||||
return await runQuery({
|
||||
label: "UPDATE_USER_BY_ID",
|
||||
queryFn: async (DB) => {
|
||||
const data = await DB.from("users")
|
||||
.where("id", o.id)
|
||||
.update({
|
||||
data: {
|
||||
...data,
|
||||
},
|
||||
})
|
||||
.returning("*");
|
||||
|
||||
const index = data ? data.pop() : null;
|
||||
return index;
|
||||
},
|
||||
errorFn: async (e) => {
|
||||
return {
|
||||
error: "UPDATE_USER",
|
||||
source: e,
|
||||
};
|
||||
},
|
||||
});
|
||||
};
|
13
node_common/data/utilities.js
Normal file
13
node_common/data/utilities.js
Normal file
@ -0,0 +1,13 @@
|
||||
import DB from "~/node_common/database";
|
||||
|
||||
export const runQuery = async ({ queryFn, errorFn, label }) => {
|
||||
let response;
|
||||
try {
|
||||
response = await queryFn(DB);
|
||||
} catch (e) {
|
||||
response = errorFn(e);
|
||||
}
|
||||
|
||||
console.log("[ database-query ]", { query: label });
|
||||
return response;
|
||||
};
|
@ -6,6 +6,6 @@ import configs from "~/knexfile";
|
||||
import knex from "knex";
|
||||
|
||||
const envConfig = configs["development"];
|
||||
const db = knex(envConfig);
|
||||
const Database = knex(envConfig);
|
||||
|
||||
module.exports = db;
|
||||
export default Database;
|
||||
|
@ -1,9 +1,23 @@
|
||||
export const init = (middleware) => {
|
||||
return (req, res) =>
|
||||
new Promise((resolve, reject) => {
|
||||
middleware(req, res, (result) => {
|
||||
if (result instanceof Error) {
|
||||
return reject(result);
|
||||
}
|
||||
return resolve(result);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const CORS = async (req, res, next) => {
|
||||
res.header("Access-Control-Allow-Origin", "*");
|
||||
|
||||
res.header(
|
||||
"Access-Control-Allow-Methods",
|
||||
"GET, POST, PATCH, PUT, DELETE, OPTIONS"
|
||||
);
|
||||
|
||||
res.header(
|
||||
"Access-Control-Allow-Headers",
|
||||
"Origin, Accept, Content-Type, Authorization"
|
9
node_common/powergate.js
Normal file
9
node_common/powergate.js
Normal file
@ -0,0 +1,9 @@
|
||||
import * as Constants from "~/node_common/constants";
|
||||
|
||||
import { createPow } from "@textile/powergate-client";
|
||||
|
||||
// NOTE(jim):
|
||||
// https://github.com/textileio/js-powergate-client
|
||||
const Powergate = createPow({ host: Constants.POWERGATE_HOST });
|
||||
|
||||
export default Powergate;
|
@ -1,34 +1,5 @@
|
||||
import * as Constants from "./constants";
|
||||
|
||||
import FS from "fs-extra";
|
||||
|
||||
export const resetFileSystem = async () => {
|
||||
console.log("[ prototype ] deleting old token and library data ");
|
||||
if (FS.existsSync(`./.data`)) {
|
||||
FS.removeSync("./.data", { recursive: true });
|
||||
}
|
||||
|
||||
console.log("[ prototype ] deleting old avatar data ");
|
||||
if (FS.existsSync(Constants.AVATAR_STORAGE_URL)) {
|
||||
FS.removeSync(Constants.AVATAR_STORAGE_URL, { recursive: true });
|
||||
}
|
||||
|
||||
console.log("[ prototype ] deleting old file data ");
|
||||
if (FS.existsSync(Constants.FILE_STORAGE_URL)) {
|
||||
FS.removeSync(Constants.FILE_STORAGE_URL, { recursive: true });
|
||||
}
|
||||
|
||||
console.log("[ prototype ] creating new avatar folder ");
|
||||
FS.mkdirSync(Constants.AVATAR_STORAGE_URL, { recursive: true });
|
||||
FS.writeFileSync(`${Constants.AVATAR_STORAGE_URL}.gitkeep`, "");
|
||||
|
||||
console.log("[ prototype ] creating new local file folder ");
|
||||
FS.mkdirSync(Constants.FILE_STORAGE_URL, { recursive: true });
|
||||
FS.writeFileSync(`${Constants.FILE_STORAGE_URL}.gitkeep`, "");
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
// NOTE(jim): Data that does not require a Powergate token.
|
||||
export const refresh = async ({ PG }) => {
|
||||
const Health = await PG.health.check();
|
||||
|
@ -7,7 +7,7 @@
|
||||
"node": ">=11 <12"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "node . --unhandled-rejections=strict",
|
||||
"dev": "NODE_TLS_REJECT_UNAUTHORIZED=0 node . --unhandled-rejections=strict",
|
||||
"start": "NODE_ENV=www node . --unhandled-rejections=strict",
|
||||
"build-delete": "rm -rf .next && rm -rf dist/mac",
|
||||
"build": "NODE_ENV=www next build",
|
||||
@ -44,7 +44,9 @@
|
||||
"@emotion/server": "11.0.0-next.12",
|
||||
"@textile/hub": "^0.3.4",
|
||||
"@textile/powergate-client": "0.1.0-beta.14",
|
||||
"@textile/threads-core": "^0.1.32",
|
||||
"babel-plugin-module-resolver": "^4.0.0",
|
||||
"bcrypt": "^5.0.0",
|
||||
"body-parser": "^1.19.0",
|
||||
"chart.js": "^2.9.3",
|
||||
"chartkick": "^3.2.0",
|
||||
@ -54,6 +56,7 @@
|
||||
"formidable": "^1.2.2",
|
||||
"fs-extra": "^9.0.1",
|
||||
"isomorphic-fetch": "^2.2.1",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"knex": "^0.20.10",
|
||||
"moment": "^2.27.0",
|
||||
"next": "^9.4.4",
|
||||
@ -65,8 +68,7 @@
|
||||
"react-tippy": "^1.3.4",
|
||||
"regenerator-runtime": "^0.13.5",
|
||||
"three": "^0.108.0",
|
||||
"uuid": "^8.0.0",
|
||||
"ws": "^7.3.0"
|
||||
"uuid": "^8.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-transform-runtime": "^7.10.4",
|
||||
|
13
pages/api/_.js
Normal file
13
pages/api/_.js
Normal file
@ -0,0 +1,13 @@
|
||||
import * as MW from "~/node_common/middleware";
|
||||
|
||||
import DB from "~/node_common/database";
|
||||
|
||||
const initCORS = MW.init(MW.CORS);
|
||||
|
||||
export default (req, res) => {
|
||||
initCORS(req, res);
|
||||
|
||||
return res
|
||||
.status(200)
|
||||
.json({ decorator: "SERVER_HEALTH_CHECK", data: req.body.data });
|
||||
};
|
71
pages/api/hydrate.js
Normal file
71
pages/api/hydrate.js
Normal file
@ -0,0 +1,71 @@
|
||||
import * as MW from "~/node_common/middleware";
|
||||
import * as Utilities from "~/node_common/utilities";
|
||||
import * as Constants from "~/node_common/constants";
|
||||
import * as Data from "~/node_common/data";
|
||||
|
||||
import { Buckets, UserAuth } from "@textile/hub";
|
||||
import { Libp2pCryptoIdentity } from "@textile/threads-core";
|
||||
|
||||
import PG from "~/node_common/powergate";
|
||||
|
||||
const initCORS = MW.init(MW.CORS);
|
||||
|
||||
export default async (req, res) => {
|
||||
initCORS(req, res);
|
||||
|
||||
const user = await Data.getUserByUsername({
|
||||
username: req.body.data.username,
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return res.status(200).json({ decorator: "SERVER_HYDRATE", error: true });
|
||||
}
|
||||
|
||||
if (user.error) {
|
||||
return res.status(200).json({ decorator: "SERVER_HYDRATE", error: true });
|
||||
}
|
||||
|
||||
const identity = await Libp2pCryptoIdentity.fromString(user.data.tokens.api);
|
||||
|
||||
const b = Buckets.withUserAuth(identity);
|
||||
// TODO(jim): Bug on the server:
|
||||
// const buckets = await b.list();
|
||||
|
||||
let data = {
|
||||
peersList: null,
|
||||
messageList: null,
|
||||
status: null,
|
||||
addrsList: null,
|
||||
info: null,
|
||||
state: null,
|
||||
local: {
|
||||
photo: null,
|
||||
name: `node`,
|
||||
settings_deals_auto_approve: false,
|
||||
},
|
||||
library: [
|
||||
{
|
||||
...Utilities.createFolder({ id: Constants.FILE_STORAGE_URL }),
|
||||
file: "Files",
|
||||
name: "Files",
|
||||
},
|
||||
],
|
||||
buckets: [],
|
||||
};
|
||||
|
||||
PG.setToken(user.data.tokens.pg);
|
||||
|
||||
const updates = await Utilities.refresh({ PG });
|
||||
const updatesWithToken = await Utilities.refreshWithToken({
|
||||
PG,
|
||||
});
|
||||
|
||||
data = await Utilities.updateStateData(data, {
|
||||
...updates,
|
||||
...updatesWithToken,
|
||||
});
|
||||
|
||||
return res
|
||||
.status(200)
|
||||
.send({ decorator: "SERVER_HYDRATE", success: true, data });
|
||||
};
|
72
pages/api/users/create.js
Normal file
72
pages/api/users/create.js
Normal file
@ -0,0 +1,72 @@
|
||||
import * as MW from "~/node_common/middleware";
|
||||
import * as Data from "~/node_common/data";
|
||||
import * as Strings from "~/common/strings";
|
||||
|
||||
import PG from "~/node_common/powergate";
|
||||
import JWT from "jsonwebtoken";
|
||||
import BCrypt from "bcrypt";
|
||||
|
||||
import { Libp2pCryptoIdentity } from "@textile/threads-core";
|
||||
|
||||
const initCORS = MW.init(MW.CORS);
|
||||
const SECRET = `$2b$13$${process.env.LOCAL_PASSWORD_SECRET}`;
|
||||
|
||||
export default async (req, res) => {
|
||||
initCORS(req, res);
|
||||
|
||||
if (Strings.isEmpty(req.body.data.email)) {
|
||||
return res
|
||||
.status(500)
|
||||
.send({ error: "An e-mail address was not provided." });
|
||||
}
|
||||
|
||||
if (Strings.isEmpty(req.body.data.password)) {
|
||||
return res.status(500).send({ error: "A password was not provided." });
|
||||
}
|
||||
|
||||
const salt = await BCrypt.genSalt(13);
|
||||
console.log({ salt });
|
||||
const hash = await BCrypt.hash(req.body.data.password, salt);
|
||||
console.log({ hash });
|
||||
const double = await BCrypt.hash(hash, salt);
|
||||
console.log({
|
||||
double,
|
||||
});
|
||||
|
||||
console.log(SECRET);
|
||||
|
||||
const triple = await BCrypt.hash(double, SECRET);
|
||||
console.log({ triple });
|
||||
|
||||
const FFS = await PG.ffs.create();
|
||||
const pg = FFS.token ? FFS.token : null;
|
||||
|
||||
// API
|
||||
const identity = await Libp2pCryptoIdentity.fromRandom();
|
||||
const api = identity.toString();
|
||||
|
||||
const user = await Data.createUser({
|
||||
email: req.body.data.email,
|
||||
password: triple,
|
||||
salt,
|
||||
username: req.body.data.username,
|
||||
data: { tokens: { pg, api } },
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return res
|
||||
.status(200)
|
||||
.json({ decorator: "SERVER_USER_CREATE", error: true });
|
||||
}
|
||||
|
||||
if (user.error) {
|
||||
return res
|
||||
.status(200)
|
||||
.json({ decorator: "SERVER_USER_CREATE", error: true });
|
||||
}
|
||||
|
||||
return res.status(200).json({
|
||||
decorator: "SERVER_USER_CREATE",
|
||||
user: { username: user.username },
|
||||
});
|
||||
};
|
20
pages/api/users/delete.js
Normal file
20
pages/api/users/delete.js
Normal file
@ -0,0 +1,20 @@
|
||||
import * as MW from "~/node_common/middleware";
|
||||
import * as Data from "~/node_common/data";
|
||||
|
||||
const initCORS = MW.init(MW.CORS);
|
||||
|
||||
export default async (req, res) => {
|
||||
initCORS(req, res);
|
||||
|
||||
const deleted = await Data.deleteUserByUsername({
|
||||
username: req.body.data.username,
|
||||
});
|
||||
|
||||
if (!deleted) {
|
||||
return res
|
||||
.status(200)
|
||||
.json({ decorator: "SERVER_USER_DELETE", error: true });
|
||||
}
|
||||
|
||||
return res.status(200).json({ decorator: "SERVER_USER_DELETE", deleted });
|
||||
};
|
11
pages/api/users/update.js
Normal file
11
pages/api/users/update.js
Normal file
@ -0,0 +1,11 @@
|
||||
import * as MW from "~/node_common/middleware";
|
||||
|
||||
import DB from "~/node_common/database";
|
||||
|
||||
const initCORS = MW.init(MW.CORS);
|
||||
|
||||
export default async (req, res) => {
|
||||
initCORS(req, res);
|
||||
|
||||
return res.status(200).json({ decorator: "SERVER_USER_UPDATE" });
|
||||
};
|
@ -65,10 +65,8 @@ const getCurrentNavigationStateById = (navigation, targetId) => {
|
||||
};
|
||||
|
||||
export const getServerSideProps = async (context) => {
|
||||
const data = await Actions.rehydrateViewer();
|
||||
|
||||
return {
|
||||
props: { ...context.query, ...data.data },
|
||||
props: { ...context.query },
|
||||
};
|
||||
};
|
||||
|
||||
@ -79,24 +77,47 @@ export default class ApplicationPage extends React.Component {
|
||||
history: [{ id: 1, scrollTop: 0 }],
|
||||
currentIndex: 0,
|
||||
data: null,
|
||||
selected: State.getSelectedState(this.props),
|
||||
viewer: State.getInitialState(this.props),
|
||||
selected: null,
|
||||
viewer: null,
|
||||
sidebar: null,
|
||||
file: null,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this._socket = new WebSocket(`ws://localhost:${this.props.wsPort}`);
|
||||
this._socket.onmessage = (m) => {
|
||||
console.log(m);
|
||||
if (m.type === "message") {
|
||||
const parsed = JSON.parse(m.data);
|
||||
async componentDidMount() {
|
||||
if (this.props.production) {
|
||||
console.log("Disabled application in production setting for now.");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (parsed.action === "UPDATE_VIEWER") {
|
||||
this.rehydrate({ data: parsed.data });
|
||||
let response = await Actions.deleteUser({
|
||||
username: "test",
|
||||
});
|
||||
|
||||
console.log(response);
|
||||
|
||||
response = await Actions.createUser({
|
||||
email: "test@test.com",
|
||||
password: "test",
|
||||
username: "test",
|
||||
});
|
||||
|
||||
if (response.error) {
|
||||
console.log("Could not create a new user");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
console.log(response);
|
||||
|
||||
response = await Actions.hydrateAuthenticatedUser({
|
||||
username: "test",
|
||||
});
|
||||
|
||||
console.log(response);
|
||||
|
||||
this.setState({
|
||||
viewer: State.getInitialState(response.data),
|
||||
selected: State.getSelectedState(response.data),
|
||||
});
|
||||
|
||||
window.addEventListener("dragenter", this._handleDragEnter);
|
||||
window.addEventListener("dragleave", this._handleDragLeave);
|
||||
@ -327,6 +348,11 @@ export default class ApplicationPage extends React.Component {
|
||||
};
|
||||
|
||||
render() {
|
||||
// TODO(jim): Render Sign In Screen.
|
||||
if (!this.state.viewer) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const navigation = NavigationData.generate(this.state.viewer.library);
|
||||
const next = this.state.history[this.state.currentIndex];
|
||||
const current = getCurrentNavigationStateById(navigation, next.id);
|
||||
|
@ -1,5 +1,6 @@
|
||||
import * as React from "react";
|
||||
import * as Constants from "~/common/constants";
|
||||
import * as Actions from "~/common/actions";
|
||||
|
||||
import { css } from "@emotion/react";
|
||||
|
||||
@ -18,6 +19,10 @@ const STYLES_HEADING = css`
|
||||
font-size: 2.88rem;
|
||||
line-height: 1.5;
|
||||
color: ${Constants.system.black};
|
||||
|
||||
@media (max-width: 768px) {
|
||||
font-size: 1rem;
|
||||
}
|
||||
`;
|
||||
|
||||
const STYLES_PARAGRAPH = css`
|
||||
@ -25,6 +30,10 @@ const STYLES_PARAGRAPH = css`
|
||||
font-size: 2.88rem;
|
||||
line-height: 1.5;
|
||||
color: ${Constants.system.pitchBlack};
|
||||
|
||||
@media (max-width: 768px) {
|
||||
font-size: 1rem;
|
||||
}
|
||||
`;
|
||||
|
||||
export const getServerSideProps = async (context) => {
|
||||
@ -34,6 +43,11 @@ export const getServerSideProps = async (context) => {
|
||||
};
|
||||
|
||||
export default class IndexPage extends React.Component {
|
||||
async componentDidMount() {
|
||||
const response = await Actions.health();
|
||||
console.log(response);
|
||||
}
|
||||
|
||||
render() {
|
||||
const title = `Slate`;
|
||||
const description =
|
||||
@ -54,9 +68,7 @@ export default class IndexPage extends React.Component {
|
||||
<a href="https://filecoin.io/">Filecoin</a>.
|
||||
<br />
|
||||
<br />
|
||||
{!this.props.hide ? (
|
||||
<a href="/application">Test Application</a>
|
||||
) : null}
|
||||
<a href="/application">Test Application (Local Only)</a>
|
||||
<br />
|
||||
<a href="/system">View Design System</a>
|
||||
</p>
|
||||
|
@ -1,3 +1,8 @@
|
||||
if (process.env.NODE_ENV !== "www") {
|
||||
console.log("[ slate ] loading dotenv");
|
||||
require("dotenv").config();
|
||||
}
|
||||
|
||||
require("@babel/register")({
|
||||
presets: ["@babel/preset-env"],
|
||||
ignore: ["node_modules", ".next"],
|
||||
|
426
server.js
426
server.js
@ -1,455 +1,49 @@
|
||||
if (process.env.NODE_ENV !== "www") {
|
||||
console.log("[ prototype ] loading dotenv");
|
||||
require("dotenv").config();
|
||||
}
|
||||
|
||||
import * as Middleware from "./common/middleware";
|
||||
import * as Strings from "./common/strings";
|
||||
import * as Middleware from "./node_common/middleware";
|
||||
import * as Utilities from "./node_common/utilities";
|
||||
import * as Constants from "./node_common/constants";
|
||||
import * as Database from "./node_common/database";
|
||||
|
||||
import { createPow, ffs } from "@textile/powergate-client";
|
||||
|
||||
// NOTE(jim):
|
||||
// https://github.com/textileio/js-powergate-client
|
||||
const PowerGate = createPow({ host: Constants.POWERGATE_HOST });
|
||||
|
||||
import { v4 as uuid } from "uuid";
|
||||
// TODO(jim):
|
||||
// CUT THIS DURING THE POSTGRES MOVE.
|
||||
import FS from "fs-extra";
|
||||
import WebSocketServer from "ws";
|
||||
import express from "express";
|
||||
import formidable from "formidable";
|
||||
import next from "next";
|
||||
import bodyParser from "body-parser";
|
||||
import compression from "compression";
|
||||
import path from "path";
|
||||
|
||||
// TODO(jim):
|
||||
// CUT THIS DURING THE POSTGRES MOVE.
|
||||
let client = null;
|
||||
let state = null;
|
||||
|
||||
const production =
|
||||
process.env.NODE_ENV === "production" || process.env.NODE_ENV === "www";
|
||||
const productionWeb = process.env.NODE_ENV === "www";
|
||||
const port = process.env.PORT || 1337;
|
||||
const wsPort = process.env.WS_PORT || 2448;
|
||||
const resetData = process.env.npm_config_reset_data;
|
||||
const app = next({ dev: !production, dir: __dirname, quiet: false });
|
||||
const nextRequestHandler = app.getRequestHandler();
|
||||
|
||||
const setIntervalViewerUpdatesUnsafe = async () => {
|
||||
if (client) {
|
||||
try {
|
||||
console.log("[ prototype ] polling: new viewer state.");
|
||||
state = await Utilities.emitState({
|
||||
state,
|
||||
client,
|
||||
PG: PowerGate,
|
||||
});
|
||||
|
||||
console.log("[ prototype ] polling: new library state.");
|
||||
state = await Utilities.refreshLibrary({
|
||||
state,
|
||||
PG: PowerGate,
|
||||
FFS: ffs,
|
||||
});
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
|
||||
setTimeout(setIntervalViewerUpdatesUnsafe, Constants.POLLING_RATE);
|
||||
};
|
||||
const handler = app.getRequestHandler();
|
||||
|
||||
app.prepare().then(async () => {
|
||||
console.log("[ prototype ] initializing ");
|
||||
|
||||
// TODO(jim):
|
||||
// CUT THIS DURING THE POSTGRES MOVE.
|
||||
state = {
|
||||
production,
|
||||
port,
|
||||
wsPort,
|
||||
token: null,
|
||||
library: null,
|
||||
status: null,
|
||||
messageList: null,
|
||||
peersList: null,
|
||||
addrsList: null,
|
||||
info: null,
|
||||
local: null,
|
||||
};
|
||||
|
||||
try {
|
||||
// TODO(jim):
|
||||
// CUT THIS DURING THE POSTGRES MOVE.
|
||||
// NOTE(daniel): Wipe all of the local data when --reset-data flag is added to npm run dev.
|
||||
if (resetData) {
|
||||
await Utilities.resetFileSystem();
|
||||
}
|
||||
|
||||
const updates = await Utilities.refresh({ PG: PowerGate });
|
||||
state = await Utilities.updateStateData(state, updates);
|
||||
console.log("[ prototype ] updated without token");
|
||||
|
||||
// NOTE(jim): This is a configuration folder with all of the client tokens.
|
||||
// TODO(jim): Unnecessary if we use a local and remote postgres.
|
||||
const dirnameData = path.join(__dirname, "/.data");
|
||||
if (!FS.existsSync(dirnameData)) {
|
||||
FS.mkdirSync(dirnameData, { recursive: true });
|
||||
}
|
||||
|
||||
// NOTE(jim): This will create a token for authentication with powergate.
|
||||
// TODO(jim): Roll this up into Postgres instead.
|
||||
const dirnamePowergate = path.join(__dirname, "/.data/powergate-token");
|
||||
if (!FS.existsSync(dirnamePowergate)) {
|
||||
const FFS = await PowerGate.ffs.create();
|
||||
state.token = FFS.token ? FFS.token : null;
|
||||
|
||||
// NOTE(jim): Write a new token file.
|
||||
if (state.token) {
|
||||
FS.writeFileSync(dirnamePowergate, state.token);
|
||||
}
|
||||
} else {
|
||||
state.token = FS.readFileSync(dirnamePowergate, "utf8");
|
||||
}
|
||||
|
||||
if (state.token) {
|
||||
console.log("[ prototype ] powergate token:", state.token);
|
||||
PowerGate.setToken(state.token);
|
||||
}
|
||||
|
||||
const tokenUpdates = await Utilities.refreshWithToken({
|
||||
PG: PowerGate,
|
||||
});
|
||||
state = await Utilities.updateStateData(state, tokenUpdates);
|
||||
console.log("[ prototype ] updated with token");
|
||||
|
||||
// NOTE(jim): Local library retrieval or creation
|
||||
// TODO(jim): Needs to support nested folders in the future.
|
||||
// TODO(jim): May consider a move to buckets.
|
||||
const dirnameLibrary = path.join(__dirname, "/.data/library.json");
|
||||
if (!FS.existsSync(dirnameLibrary)) {
|
||||
const librarySchema = {
|
||||
library: [
|
||||
{
|
||||
...Utilities.createFolder({ id: Constants.FILE_STORAGE_URL }),
|
||||
file: "Files",
|
||||
name: "Files",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
FS.writeFileSync(dirnameLibrary, JSON.stringify(librarySchema));
|
||||
state.library = librarySchema.library;
|
||||
} else {
|
||||
const parsedLibrary = FS.readFileSync(dirnameLibrary, "utf8");
|
||||
state.library = JSON.parse(parsedLibrary).library;
|
||||
}
|
||||
|
||||
// NOTE(jim): Local settings retrieval or creation
|
||||
// TODO(jim): Move this to postgres later.
|
||||
const dirnameLocalSettings = path.join(
|
||||
__dirname,
|
||||
"/.data/local-settings.json"
|
||||
);
|
||||
if (!FS.existsSync(dirnameLocalSettings)) {
|
||||
const localSettingsSchema = {
|
||||
local: {
|
||||
photo: null,
|
||||
name: `node-${uuid()}`,
|
||||
settings_deals_auto_approve: false,
|
||||
},
|
||||
};
|
||||
|
||||
FS.writeFileSync(
|
||||
dirnameLocalSettings,
|
||||
JSON.stringify(localSettingsSchema)
|
||||
);
|
||||
state.local = localSettingsSchema.local;
|
||||
} else {
|
||||
const parsedLocal = FS.readFileSync(dirnameLocalSettings, "utf8");
|
||||
state.local = JSON.parse(parsedLocal).local;
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
console.log('[ prototype ] "/" -- WILL REDIRECT TO /SYSTEM ');
|
||||
console.log(
|
||||
"[ prototype ] SLATE WILL NOT RUN LOCALLY UNTIL YOU HAVE "
|
||||
);
|
||||
console.log("[ prototype ] PROPERLY CONFIGURED POWERGATE AND ");
|
||||
console.log(
|
||||
"[ prototype ] CONNECTED TO THE FILECOIN NETWORK (DEVNET/TESTNET) "
|
||||
);
|
||||
}
|
||||
|
||||
const server = express();
|
||||
// TODO(jim): Temporarily disable web sockets for web production
|
||||
// since we have no web version of Slate yet.
|
||||
if (!productionWeb) {
|
||||
const WSS = new WebSocketServer.Server({ port: wsPort });
|
||||
|
||||
WSS.on("connection", (s) => {
|
||||
// TODO(jim): Suppport more than one client.
|
||||
client = s;
|
||||
|
||||
s.on("close", function() {
|
||||
s.send(JSON.stringify({ action: null, data: "closed" }));
|
||||
});
|
||||
|
||||
s.send(JSON.stringify({ action: null, data: "connected" }));
|
||||
});
|
||||
}
|
||||
|
||||
if (productionWeb) {
|
||||
server.use(compression());
|
||||
}
|
||||
|
||||
server.use(Middleware.CORS);
|
||||
server.use("/public", express.static("public"));
|
||||
server.use(bodyParser.json());
|
||||
server.use(
|
||||
bodyParser.urlencoded({
|
||||
extended: false,
|
||||
})
|
||||
);
|
||||
|
||||
server.post("/_/viewer", async (req, res) => {
|
||||
let data = state;
|
||||
|
||||
if (!productionWeb) {
|
||||
const updates = await Utilities.refresh({ PG: PowerGate });
|
||||
const updatesWithToken = await Utilities.refreshWithToken({
|
||||
PG: PowerGate,
|
||||
});
|
||||
data = await Utilities.updateStateData(data, {
|
||||
...updates,
|
||||
...updatesWithToken,
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(200).send({ success: true, data });
|
||||
});
|
||||
|
||||
server.post("/_/deals/storage", async (req, res) => {
|
||||
if (Strings.isEmpty(req.body.src)) {
|
||||
return res.status(500).send({ success: false });
|
||||
}
|
||||
|
||||
const localPath = `.${req.body.src}`;
|
||||
const buffer = FS.readFileSync(localPath);
|
||||
const { cid } = await PowerGate.ffs.addToHot(buffer);
|
||||
const { jobId } = await PowerGate.ffs.pushConfig(cid);
|
||||
|
||||
// TODO(jim): Refactor this so we repeat this less often.
|
||||
let write = false;
|
||||
for (let i = 0; i < state.library.length; i++) {
|
||||
for (let j = 0; j < state.library[i].children.length; j++) {
|
||||
if (localPath === state.library[i].children[j].id) {
|
||||
state.library[i].children[j].job_id = jobId;
|
||||
state.library[i].children[j].cid = cid;
|
||||
state.library[i].children[j].storage_status = 1;
|
||||
write = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE(jim): Writes the updated deal state.
|
||||
if (write) {
|
||||
const dirnameLibrary = path.join(__dirname, "/.data/library.json");
|
||||
FS.writeFileSync(
|
||||
dirnameLibrary,
|
||||
JSON.stringify({ library: state.library })
|
||||
);
|
||||
}
|
||||
|
||||
state = await Utilities.emitState({ state, client, PG: PowerGate });
|
||||
return res.status(200).send({ success: true, cid, jobId });
|
||||
});
|
||||
|
||||
server.post("/_/storage/:file", async (req, res) => {
|
||||
const form = formidable({
|
||||
multiples: true,
|
||||
uploadDir: Constants.FILE_STORAGE_URL,
|
||||
});
|
||||
|
||||
form.parse(req, async (error, fields, files) => {
|
||||
if (error) {
|
||||
return res.status(500).send({ error });
|
||||
} else {
|
||||
// TODO(jim): Need to support other file types.
|
||||
if (!files.image) {
|
||||
console.error("[ prototype ] File type unspported", files);
|
||||
return res
|
||||
.status(500)
|
||||
.send({ error: "File type unsupported", files });
|
||||
}
|
||||
|
||||
const newPath = form.uploadDir + req.params.file;
|
||||
FS.rename(files.image.path, newPath, function(err) {});
|
||||
|
||||
const localFile = Utilities.createFile({
|
||||
id: newPath,
|
||||
data: files.image,
|
||||
});
|
||||
|
||||
// TODO(jim): Messy, refactor.
|
||||
let pushed = false;
|
||||
for (let i = 0; i < state.library.length; i++) {
|
||||
if (!pushed) {
|
||||
state.library[i].children.push(localFile);
|
||||
pushed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE(jim): Writes the added file.
|
||||
if (pushed) {
|
||||
const dirnameLibrary = path.join(__dirname, "/.data/library.json");
|
||||
FS.writeFileSync(
|
||||
dirnameLibrary,
|
||||
JSON.stringify({ library: state.library })
|
||||
);
|
||||
}
|
||||
|
||||
state = await Utilities.emitState({
|
||||
state,
|
||||
client,
|
||||
PG: PowerGate,
|
||||
});
|
||||
|
||||
return res.status(200).send({ success: true, file: localFile });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
server.post("/_/upload/avatar", async (req, res) => {
|
||||
const form = formidable({
|
||||
multiples: true,
|
||||
uploadDir: Constants.AVATAR_STORAGE_URL,
|
||||
});
|
||||
|
||||
form.parse(req, async (error, fields, files) => {
|
||||
if (error) {
|
||||
return res.status(500).send({ error });
|
||||
} else {
|
||||
const newName = `avatar-${uuid()}.png`;
|
||||
const newPath = form.uploadDir + newName;
|
||||
FS.rename(files.image.path, newPath, function(err) {});
|
||||
|
||||
// NOTE(jim): updates avatar photo.
|
||||
state.local.photo = __dirname + `/static/system/${newName}`;
|
||||
const dirnameLocalSettings = path.join(
|
||||
__dirname,
|
||||
"/.data/local-settings.json"
|
||||
);
|
||||
FS.writeFileSync(
|
||||
dirnameLocalSettings,
|
||||
JSON.stringify({ local: { ...state.local } })
|
||||
);
|
||||
|
||||
state = await Utilities.emitState({
|
||||
state,
|
||||
client,
|
||||
PG: PowerGate,
|
||||
});
|
||||
|
||||
return res.status(200).send({ success: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
server.post("/_/settings", async (req, res) => {
|
||||
let data;
|
||||
try {
|
||||
data = await PowerGate.ffs.setDefaultConfig(req.body.config);
|
||||
} catch (e) {
|
||||
return res.status(500).send({ error: e.message });
|
||||
}
|
||||
|
||||
state = await Utilities.emitState({ state, client, PG: PowerGate });
|
||||
return res.status(200).send({ success: true, data });
|
||||
});
|
||||
|
||||
server.post("/_/local-settings", async (req, res) => {
|
||||
state.local = { ...state.local, ...req.body.local };
|
||||
const dirnameLocalSettings = path.join(
|
||||
__dirname,
|
||||
"/.data/local-settings.json"
|
||||
);
|
||||
FS.writeFileSync(
|
||||
dirnameLocalSettings,
|
||||
JSON.stringify({ local: { ...state.local } })
|
||||
);
|
||||
state = await Utilities.emitState({ state, client, PG: PowerGate });
|
||||
return res.status(200).send({ success: true });
|
||||
});
|
||||
|
||||
server.post("/_/wallet/create", async (req, res) => {
|
||||
let data;
|
||||
try {
|
||||
data = await PowerGate.ffs.newAddr(
|
||||
req.body.name,
|
||||
req.body.type,
|
||||
req.body.makeDefault
|
||||
);
|
||||
} catch (e) {
|
||||
return res.status(500).send({ error: e.message });
|
||||
}
|
||||
|
||||
state = await Utilities.emitState({ state, client, PG: PowerGate });
|
||||
return res.status(200).send({ success: true, data });
|
||||
});
|
||||
|
||||
server.post("/_/wallet/send", async (req, res) => {
|
||||
let data;
|
||||
try {
|
||||
data = await PowerGate.ffs.sendFil(
|
||||
req.body.source,
|
||||
req.body.target,
|
||||
req.body.amount
|
||||
);
|
||||
} catch (e) {
|
||||
return res.status(500).send({ error: e.message });
|
||||
}
|
||||
|
||||
state = await Utilities.emitState({ state, client, PG: PowerGate });
|
||||
return res
|
||||
.status(200)
|
||||
.send({ success: true, data: { ...data, ...req.body } });
|
||||
});
|
||||
|
||||
server.get("/application", async (req, res) => {
|
||||
return app.render(req, res, "/application", {
|
||||
wsPort,
|
||||
wsPort: null,
|
||||
production: productionWeb,
|
||||
});
|
||||
});
|
||||
|
||||
server.get("/", async (req, res) => {
|
||||
return app.render(req, res, "/", { hide: productionWeb });
|
||||
return app.render(req, res, "/");
|
||||
});
|
||||
|
||||
server.get("*", async (req, res) => {
|
||||
return nextRequestHandler(req, res, req.url);
|
||||
server.all("*", async (req, res) => {
|
||||
return handler(req, res, req.url);
|
||||
});
|
||||
|
||||
server.listen(port, async (err) => {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
server.listen(port, async (e) => {
|
||||
if (e) throw e;
|
||||
|
||||
console.log(`[ prototype ] client: http://localhost:${port}`);
|
||||
console.log(`[ prototype ] constants:`, Constants);
|
||||
console.log(
|
||||
`[ prototype ] .env postgres hostname: ${process.env.POSTGRES_HOSTNAME}`
|
||||
);
|
||||
|
||||
if (!productionWeb) {
|
||||
await setIntervalViewerUpdatesUnsafe();
|
||||
}
|
||||
console.log(`[ slate ] client: http://localhost:${port}`);
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user