api: lands get slate and add data to slate by id

This commit is contained in:
@wwwjim 2020-07-28 16:08:38 -07:00
parent 9bb6ac2eda
commit d1048382d7
11 changed files with 507 additions and 7 deletions

View File

@ -21,6 +21,14 @@ export const copyText = (str) => {
return true;
};
export const getKey = (text) => {
if (isEmpty(text)) {
return null;
}
return text.replace("Basic ", "");
};
export const createSlug = (text) => {
if (isEmpty(text)) {
return "untitled";

View File

@ -271,7 +271,7 @@ export const injectCodeBlockStyles = () => css`
.token.prolog,
.token.doctype,
.token.cdata {
color: #444;
color: #FF00FF;
}
.token.punctuation {

View File

@ -67,7 +67,11 @@ class CodeBlock extends React.Component {
const textMap = codeBlockToken;
return (
<div css={STYLES_CODE_BLOCK} className="language-javascript">
<div
css={STYLES_CODE_BLOCK}
className="language-javascript"
style={this.props.style}
>
{textMap.map((element, index) => {
return (
<div css={STYLES_LINE} key={`${element}-${index}`}>

View File

@ -14,6 +14,7 @@ import createAPIKeyForUserId from "~/node_common/data/methods/create-api-key-for
import deleteAPIKeyById from "~/node_common/data/methods/delete-api-key-by-id";
import deleteAPIKeysForUserId from "~/node_common/data/methods/delete-api-keys-for-user-id";
import getAPIKey from "~/node_common/data/methods/get-api-key";
import getAPIKeyByKey from "~/node_common/data/methods/get-api-key-by-key";
import getAPIKeysByUserId from "~/node_common/data/methods/get-api-keys-by-user-id";
export {
@ -34,5 +35,6 @@ export {
deleteAPIKeyById,
deleteAPIKeysForUserId,
getAPIKey,
getAPIKeyByKey,
getAPIKeysByUserId,
};

View File

@ -0,0 +1,31 @@
import { runQuery } from "~/node_common/data/utilities";
export default async ({ key }) => {
return await runQuery({
label: "GET_API_KEY_BY_KEY",
queryFn: async (DB) => {
const query = await DB.select("*")
.from("keys")
.where({ key })
.first();
console.log(query);
if (!query || query.error) {
return null;
}
if (query.id) {
return query;
}
return null;
},
errorFn: async (e) => {
return {
error: "GET_API_KEY_BY_KEY",
source: e,
};
},
});
};

View File

@ -20,7 +20,7 @@ export default async ({ id, slatename, updated_at, data }) => {
},
errorFn: async (e) => {
return {
error: "UPDATE_SLATE",
error: "UPDATE_SLATE_BY_ID",
source: e,
};
},

View File

@ -32,7 +32,7 @@ export default async ({ id, data, username, salt, password }) => {
},
errorFn: async (e) => {
return {
error: "UPDATE_USER",
error: "UPDATE_USER_BY_ID",
source: e,
};
},

View File

@ -1,7 +1,39 @@
import * as React from "react";
import * as System from "../dist";
import { css } from "@emotion/react";
const STYLES_FILE_HIDDEN = css`
height: 1px;
width: 1px;
opacity: 0;
visibility: hidden;
position: fixed;
top: -1px;
left: -1px;
`;
export default class SlateReactSystemPage extends React.Component {
_handleUpload = async (e) => {
e.persist();
const url = "/api/v1/upload-data/--";
let file = e.target.files[0];
let data = new FormData();
data.append("image", file);
const response = await fetch(url, {
method: "POST",
headers: {
Authorization: "Basic --",
},
body: data,
});
const json = await response.json();
console.log(json);
};
render() {
console.log(System.Constants);
@ -15,9 +47,21 @@ export default class SlateReactSystemPage extends React.Component {
correctly.
<br />
<br />
<System.ButtonPrimary>Primary</System.ButtonPrimary> &nbsp;
<System.ButtonSecondary>Secondary</System.ButtonSecondary> &nbsp;
<System.ButtonDisabled>Disabled</System.ButtonDisabled>
<div style={{ marginTop: 24 }}>
<input
css={STYLES_FILE_HIDDEN}
type="file"
id="file"
onChange={this._handleUpload}
/>
<System.ButtonPrimary
style={{ margin: "0 16px 16px 0" }}
type="label"
htmlFor="file"
>
Pick avatar
</System.ButtonPrimary>
</div>
</System.P>
</div>
);

94
pages/api/v1/get-slate.js Normal file
View File

@ -0,0 +1,94 @@
import * as MW from "~/node_common/middleware";
import * as Utilities from "~/node_common/utilities";
import * as Data from "~/node_common/data";
import * as Strings from "~/common/strings";
import * as Powergate from "~/node_common/powergate";
const initCORS = MW.init(MW.CORS);
export default async (req, res) => {
initCORS(req, res);
if (Strings.isEmpty(req.headers.authorization)) {
return res.status(404).send({
decorator: "SERVER_API_KEY_MISSING",
error: true,
});
}
const parsed = Strings.getKey(req.headers.authorization);
const key = await Data.getAPIKeyByKey({
key: parsed,
});
if (!key) {
return res.status(403).send({
decorator: "V1_GET_SLATE_NOT_FOUND",
error: true,
});
}
if (key.error) {
return res.status(500).send({
decorator: "V1_GET_SLATE_NOT_FOUND",
error: true,
});
}
const user = await Data.getUserById({
id: key.owner_id,
});
if (!user) {
return res
.status(404)
.json({ decorator: "V1_GET_SLATE_USER_NOT_FOUND", error: true });
}
if (user.error) {
return res
.status(500)
.json({ decorator: "V1_GET_SLATE_USER_NOT_FOUND", error: true });
}
let slateId = req.body.data ? req.body.data.id : null;
let slate;
if (Strings.isEmpty(slateId)) {
const slates = await Data.getSlatesByUserId({ userId: user.id });
if (!slates) {
return res
.status(404)
.json({ decorator: "V1_GET_SLATE_SLATE_NOT_FOUND", error: true });
}
if (slates.error || !slates.length) {
return res
.status(500)
.json({ decorator: "V1_GET_SLATE_SLATE_NOT_FOUND", error: true });
}
return res
.status(200)
.json({ decorator: "V1_GET_SLATE_MOST_RECENT", slate: slates[0] });
}
slate = await Data.getSlateById({ id: slateId });
if (!slate) {
return res.status(404).json({
decorator: "V1_GET_SLATE_NOT_FOUND",
error: true,
});
}
if (slate.error) {
return res.status(500).json({
decorator: "V1_GET_SLATE_SLATE_NOT_FOUND",
error: true,
});
}
return res.status(200).json({ decorator: "V1_GET_SLATE", slate });
};

View File

@ -0,0 +1,181 @@
import * as MW from "~/node_common/middleware";
import * as Constants from "~/node_common/constants";
import * as Data from "~/node_common/data";
import * as Utilities from "~/node_common/utilities";
import * as LibraryManager from "~/node_common/managers/library";
import * as Strings from "~/common/strings";
import FORM from "formidable";
import FS from "fs-extra";
const initCORS = MW.init(MW.CORS);
export const config = {
api: {
bodyParser: false,
},
};
export default async (req, res) => {
initCORS(req, res);
if (Strings.isEmpty(req.headers.authorization)) {
return res.status(404).send({
decorator: "SERVER_API_KEY_MISSING",
error: true,
});
}
// NOTE(jim): Get Slate to make sure we can add to it.
let slate = await Data.getSlateById({ id: req.query.id });
if (!slate) {
return res.status(404).json({
decorator: "V1_SERVER_UPLOAD_SLATE_NOT_FOUND",
error: true,
});
}
if (slate.error) {
return res.status(500).json({
decorator: "V1_SERVER_UPLOAD_SLATE_NOT_FOUND",
error: true,
});
}
const parsed = Strings.getKey(req.headers.authorization);
const key = await Data.getAPIKeyByKey({
key: parsed,
});
if (!key) {
return res.status(403).send({
decorator: "V1_SERVER_API_KEY_NOT_FOUND",
error: true,
});
}
if (key.error) {
return res.status(500).send({
decorator: "V1_SERVER_API_KEY_NOT_FOUND",
error: true,
});
}
const f = new FORM.IncomingForm();
f.uploadDir = Constants.FILE_STORAGE_URL;
f.keepExtensions = true;
f.parse(req, async (e, fields, files) => {
if (e) {
return res
.status(500)
.send({ decorator: "V1_SERVER_UPLOAD_PARSE_FAILURE", error: true });
}
if (!files.image) {
return res
.status(500)
.send({ decorator: "V1_SERVER_UPLOAD_NOT_IMAGE_TYPE", error: true });
}
const path = files.image._writeStream.path;
const data = LibraryManager.createLocalDataIncomplete(files.image);
// TODO(jim): Send this file to buckets.
const id = Utilities.getIdFromCookie(req);
const user = await Data.getUserById({
id: key.owner_id,
});
const {
buckets,
bucketKey,
bucketName,
} = await Utilities.getBucketAPIFromUserToken(user.data.tokens.api);
let readFile;
let push;
try {
// NOTE(jim): Push pathPath to your bucket.
readFile = await FS.readFileSync(path).buffer;
push = await buckets.pushPath(bucketKey, data.name, readFile);
} catch (e) {
console.log(e);
return res
.status(500)
.send({ decorator: "V1_SERVER_BUCKETS_PUSH_ISSUE", error: true });
}
// NOTE(jim): Update your user flag.
const updated = LibraryManager.updateDataIPFS(data, {
ipfs: push.path.path,
});
// NOTE(jim): Update your library
const updatedUserData = LibraryManager.addData({ user, data: updated });
// NOTE(jim): Update your user
const response = await Data.updateUserById({
id: user.id,
data: updatedUserData,
});
// NOTE(jim): Remove the file when you're done with it.
await FS.unlinkSync(`./${path}`);
// NOTE(jim): Make the call again to handle any time difference.
slate = await Data.getSlateById({ id: req.query.id });
if (!slate) {
return res.status(404).json({
decorator: "V1_SERVER_UPLOAD_SLATE_NOT_FOUND",
error: true,
});
}
if (slate.error) {
return res.status(500).json({
decorator: "V1_SERVER_UPLOAD_SLATE_NOT_FOUND",
error: true,
});
}
// NOTE(jim): We have a key, we know the slate is real, so add to it.
const update = await Data.updateSlateById({
id: slate.id,
updated_at: new Date(),
data: {
...slate.data,
objects: [
{
id: updated.id,
ownerId: user.id,
name: updated.name,
url: `https://hub.textile.io${updated.ipfs}`,
},
...slate.data.objects,
],
},
});
if (!update) {
return res.status(500).json({
decorator: "V1_SERVER_UPLOAD_TO_SLATE_ERROR",
error: true,
});
}
if (update.error) {
return res.status(500).json({
decorator: "V1_SERVER_UPLOAD_TO_SLATE_ERROR",
error: true,
});
}
return res.status(200).send({
decorator: "V1_UPLOAD_DATA_TO_SLATE",
data: updated,
slate: update,
});
});
};

View File

@ -7,6 +7,7 @@ import * as SVG from "~/components/system/svg";
import { css } from "@emotion/react";
import ScenePage from "~/components/core/ScenePage";
import CodeBlock from "~/components/system/CodeBlock";
const STYLES_KEY = css`
display: flex;
@ -82,6 +83,77 @@ class Key extends React.Component {
}
}
const EXAMPLE_GET_SLATE = (
key,
slateId
) => `// NOTE: set a slate by ID in an async/await function
const response = await fetch('https://slate.host/api/v1/get-slate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
// NOTE: your API key
Authorization: 'Basic ${key}',
},
body: JSON.stringify({ data: {
// NOTE: your slate id
id: ${slateId}
}})
});
const json = await response.json();
console.log(json);`;
const EXAMPLE_GET_SLATE_RESPONSE = (
key
) => `// NOTE: get a slate by ID JSON response
{
data: {
id: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
updated_at: '2020-07-27T09:04:53.007Z',
created_at: '2020-07-27T09:04:53.007Z',
published_at: '2020-07-27T09:04:53.007Z',
slatename: 'slatename',
data: {
name: "slatename",
public: true,
objects: [
{
id: "data-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
name: "data-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
ownerId: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
url: "https://slate.host/static/social.png"
}
],
ownerId: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
}
}`;
const EXAMPLE_UPLOAD_TO_SLATE = (key, slateId) => `// NOTE
// Upload data to a Slate by id in an async/await function.
// Uses event data from a type="file" input.
// NOTE: your slate id
const url = 'https://slate.host/api/v1/upload-data/${slateId}';
let file = e.target.files[0];
let data = new FormData();
data.append("image", file);
const response = await fetch(url, {
method: 'POST',
headers: {
// NOTE: your API key
Authorization: 'Basic ${key}',
},
body: data
});
const json = await response.json();
console.log(json);`;
export default class SceneSettingsDeveloper extends React.Component {
state = {
loading: false,
@ -126,7 +198,41 @@ export default class SceneSettingsDeveloper extends React.Component {
this.setState({ loading: false });
};
async componentDidMount() {
if (!this.props.viewer.keys) {
return;
}
if (!this.props.viewer.keys.length) {
return;
}
const response = await fetch("/api/v1/get-slate", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Basic ${this.props.viewer.keys[0].key}`,
},
});
const json = await response.json();
console.log(json);
}
render() {
let key;
if (this.props.viewer.keys) {
if (this.props.viewer.keys.length) {
key = this.props.viewer.keys[0].key;
}
}
let slateId = "your-slate-uuid-v4-value";
if (this.props.viewer.slates) {
if (this.props.viewer.slates.length) {
slateId = this.props.viewer.slates[0].id;
}
}
return (
<ScenePage>
<System.H1>API Key</System.H1>
@ -148,6 +254,36 @@ export default class SceneSettingsDeveloper extends React.Component {
Generate
</System.ButtonPrimary>
</div>
{key ? (
<React.Fragment>
<System.H2 style={{ marginTop: 64 }}>Usage (JavaScript)</System.H2>
<System.DescriptionGroup
style={{ marginTop: 48 }}
label="Get slate by ID"
description="If you have the ID of your slate you can make a request for it. If you don't provide an ID you will get back the most recent slate you have made."
/>
<CodeBlock
children={EXAMPLE_GET_SLATE(key, slateId)}
style={{ maxWidth: "768px" }}
/>
<br />
<br />
<CodeBlock
children={EXAMPLE_GET_SLATE_RESPONSE(key)}
style={{ maxWidth: "768px" }}
/>
<System.DescriptionGroup
style={{ marginTop: 48 }}
label="Upload data to slate by ID"
description="You can use an HTML input field to get a file from the JavaScript event and upload that file to a slate of your choice. You must have the correct slate ID for this to work."
/>
<CodeBlock
children={EXAMPLE_UPLOAD_TO_SLATE(key, slateId)}
style={{ maxWidth: "768px" }}
/>
</React.Fragment>
) : null}
</ScenePage>
);
}