Compare commits

...

14 Commits

Author SHA1 Message Date
Ishan Jain be4da1823f
updates!
1. Removed all codes which adds ability to execute programs and display
  results
2. Added more options for the save duration
2023-02-11 21:37:33 +05:30
Ishan Jain ba55a222f5
updated dependencies, Added listener to accept pastes directly in terminal 2023-02-11 19:20:45 +05:30
Konrad Borowski aa2b003d6b
Merge pull request #354 from pastebinrun/a6dcdependabot/npm_and_yarn/eslint-8.34.0
Bump eslint from 8.33.0 to 8.34.0
2023-02-11 06:47:30 +00:00
Konrad Borowski b073e6f7be
Merge pull request #355 from pastebinrun/6e2adependabot/npm_and_yarn/codemirror/lang-html-6.4.2
Bump @codemirror/lang-html from 6.4.1 to 6.4.2
2023-02-11 06:47:15 +00:00
dependabot[bot] 950e13db3e
Bump @codemirror/lang-html from 6.4.1 to 6.4.2
Bumps [@codemirror/lang-html](https://github.com/codemirror/lang-html) from 6.4.1 to 6.4.2.
- [Release notes](https://github.com/codemirror/lang-html/releases)
- [Changelog](https://github.com/codemirror/lang-html/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codemirror/lang-html/compare/6.4.1...6.4.2)

---
updated-dependencies:
- dependency-name: "@codemirror/lang-html"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-10 23:01:45 +00:00
dependabot[bot] 354be15b78
Bump eslint from 8.33.0 to 8.34.0
Bumps [eslint](https://github.com/eslint/eslint) from 8.33.0 to 8.34.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v8.33.0...v8.34.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-10 23:01:25 +00:00
Konrad Borowski a6f7fad4d2
Merge pull request #348 from pastebinrun/update-rust-dependencies
Update Rust depdendencies
2023-02-10 07:59:29 +00:00
Konrad Borowski 429be36610
Merge pull request #349 from pastebinrun/623fdependabot/npm_and_yarn/prettier-2.8.4
Bump prettier from 2.8.3 to 2.8.4
2023-02-10 07:59:27 +00:00
Konrad Borowski 823c5f93f6
Merge pull request #350 from pastebinrun/c1a0dependabot/npm_and_yarn/codemirror/view-6.8.1
Bump @codemirror/view from 6.8.0 to 6.8.1
2023-02-10 07:59:24 +00:00
Konrad Borowski 42d2cc283a
Merge pull request #353 from pastebinrun/fa09dependabot/cargo/serde_json-1.0.93
Bump serde_json from 1.0.92 to 1.0.93
2023-02-10 07:59:18 +00:00
dependabot[bot] 464e879108
Bump serde_json from 1.0.92 to 1.0.93
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.92 to 1.0.93.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.92...v1.0.93)

---
updated-dependencies:
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-08 22:07:40 +00:00
Konrad Borowski 9f77b79048 Update Rust depdendencies 2023-02-08 19:26:09 +01:00
dependabot[bot] c0250dce0d
Bump @codemirror/view from 6.8.0 to 6.8.1
Bumps [@codemirror/view](https://github.com/codemirror/view) from 6.8.0 to 6.8.1.
- [Release notes](https://github.com/codemirror/view/releases)
- [Changelog](https://github.com/codemirror/view/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codemirror/view/compare/6.8.0...6.8.1)

---
updated-dependencies:
- dependency-name: "@codemirror/view"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-08 18:23:58 +00:00
dependabot[bot] 94d0a25aee
Bump prettier from 2.8.3 to 2.8.4
Bumps [prettier](https://github.com/prettier/prettier) from 2.8.3 to 2.8.4.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/2.8.3...2.8.4)

---
updated-dependencies:
- dependency-name: prettier
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-08 18:23:56 +00:00
42 changed files with 988 additions and 3312 deletions

1399
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -7,28 +7,35 @@ license = "AGPL-3.0-or-later"
build = "buildSrc/build.rs"
[dependencies]
ammonia = "3.3.0"
chrono = "0.4.23"
diesel = { version = "1.4.6", features = ["chrono"] }
diesel_migrations = "1.4.0"
futures = "0.3.26"
itertools = "0.10.5"
log = "0.4.14"
once_cell = "1.17.0"
prometheus = { version = "0.13.3", features = ["process"], default-features = false }
prometheus = { version = "0.13.3", features = [
"process",
], default-features = false }
pulldown-cmark = { version = "0.9.2", default-features = false }
rand = "0.8.3"
reqwest = { version = "0.11.14", features = ["json"] }
rocket = { version = "=0.5.0-rc.2", features = ["json"] }
rocket_dyn_templates = { version = "=0.1.0-rc.2", features = ["tera"] }
rocket_sync_db_pools = { version = "=0.1.0-rc.2", features = ["diesel_postgres_pool"] }
rocket_sync_db_pools = { version = "=0.1.0-rc.2", features = [
"diesel_postgres_pool",
] }
rust-embed = "6.4.2"
serde = { version = "1.0.152", features = ["derive"] }
serde_json = "1.0.92"
serde_json = "1.0.93"
serde_with = "2.2.0"
time-parse = "0.2.0"
tokio = { version = "1.25.0", features = ["rt"] }
tokio-signal = "0.2.9"
[build-dependencies]
serde = { version = "1.0.152", features = ["derive"] }
serde_json = "1.0.92"
serde_json = "1.0.93"
[features]
database_tests = []

View File

@ -1,31 +0,0 @@
// pastebin.run
// Copyright (C) 2022 Konrad Borowski
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import { createSignal } from "solid-js";
import { render, screen } from "solid-testing-library";
import { expect, test } from "vitest";
import StandardInput from "../js/views/StandardInput";
test("StandardInput can become visible", async () => {
const [visible, setVisible] = createSignal(false);
const [, setStandardInput] = createSignal("");
render(() => (
<StandardInput visible={visible} setStandardInput={setStandardInput} />
));
expect(screen.queryByRole("textbox")).toBeNull();
setVisible(true);
expect(screen.getByRole("textbox")).toBeEmptyDOMElement();
});

View File

@ -18,8 +18,6 @@ import { render } from "solid-js/web";
import App from "./views/App";
export default function createEditor(editor: Element) {
const markdown = editor.querySelector("#markdown") as Element;
const languages = editor.querySelector("select") as HTMLSelectElement;
const autoDelete = editor.querySelector(".autodelete-text") as Element;
const rawPasteElement = editor.querySelector(".rawpaste-text") as Element;
const code = (editor.querySelector("textarea") as HTMLTextAreaElement).value;
@ -27,8 +25,6 @@ export default function createEditor(editor: Element) {
render(
() => (
<App
markdown={markdown}
languages={languages}
autoDelete={autoDelete}
rawPasteElement={rawPasteElement}
code={code}

View File

@ -1,24 +0,0 @@
// pastebin.run
// Copyright (C) 2022 Konrad Borowski
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
type Wrapper = {
identifier: string;
label: string;
isAsm: boolean;
isFormatter: boolean;
};
export default Wrapper;

View File

@ -1,23 +0,0 @@
// pastebin.run
// Copyright (C) 2022 Konrad Borowski
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import Wrapper from "./Wrapper";
type WrapperOptions = {
wrapper: Wrapper;
compilerOptions: string;
};
export default WrapperOptions;

View File

@ -39,5 +39,5 @@ export function getEditorTypeSignal(): Signal<string> {
}
export function getTabIndentationSignal(): Signal<string> {
return createLocalStorageState("tabIndentation", "false");
return createLocalStorageState("tabIndentation", "true");
}

View File

@ -15,60 +15,36 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import { createSignal, Show } from "solid-js";
import WrapperOptions from "../models/WrapperOptions";
import Editor from "./Editor";
import LanguageSelector from "./LanguageSelector";
import Output from "./Output";
import StandardInput from "./StandardInput";
import WrapperBar from "./WrapperBar";
export default function App({
markdown,
languages,
autoDelete,
rawPasteElement,
code,
}: {
markdown: Element | null;
languages: HTMLSelectElement;
autoDelete: Element | null;
rawPasteElement: Element | null;
code: string;
}) {
let form: HTMLFormElement | undefined;
const [isPaste, setIsPaste] = createSignal(true);
const [currentLanguage, setCurrentLanguage] = createSignal(
languages.selectedOptions[0]?.value
);
const [standardInputVisible, setStandardInputVisible] = createSignal(false);
const [codeView, setCodeView] = createSignal({ code: "" });
const [standardInput, setStandardInput] = createSignal("");
const [wrapperOptions, setWrapperOptions] = createSignal<WrapperOptions>();
const [_, setCodeView] = createSignal({ code: "" });
const [label, setLabel] = createSignal<Element>();
return (
<form action="/" method="post" ref={(e) => (form = e)}>
{markdown}
<Show when={isPaste()}>{autoDelete}</Show>
<div id="toolbar">
<LanguageSelector
setCurrentLanguage={setCurrentLanguage}
languages={languages}
/>
<WrapperBar
currentLanguage={currentLanguage}
setStandardInputVisible={setStandardInputVisible}
codeView={codeView}
runEvaluation={(wrapper, compilerOptions) => {
setWrapperOptions();
setWrapperOptions({ wrapper, compilerOptions });
}}
/>
<Show when={isPaste()}>{rawPasteElement}</Show>
<span id="right-buttons">
<button type="submit" name="share" value="share24">
Share (delete after 24 hours)
</button>{" "}
<button type="submit" name="share" value="share">
<select name="duration" id="duration">
<option value="1h">1 Hour</option>
<option value="3h">3 Hours</option>
<option value="24h">24 Hours</option>
<option value="3d">3 Days</option>
<option value="7d">7 Days</option>
<option value="90d">90 Days</option>
</select>{" "}
<button type="submit" name="share">
Share
</button>
</span>
@ -81,29 +57,13 @@ export default function App({
code={code}
onInput={() => {
setIsPaste(false);
setWrapperOptions();
}}
currentLanguage={currentLanguage}
form={form as HTMLFormElement}
setCodeView={setCodeView}
setLabel={setLabel}
/>
</div>
<StandardInput
visible={standardInputVisible}
setStandardInput={setStandardInput}
/>
</div>
<Show when={wrapperOptions()} keyed>
{(wrapperOptions) => (
<Output
codeView={codeView()}
stdin={standardInput()}
wrapperOptions={wrapperOptions}
setWrapperOptions={setWrapperOptions}
/>
)}
</Show>
</div>
</form>
);

View File

@ -14,29 +14,26 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import { EditorView, basicSetup } from "codemirror";
import { indentWithTab } from "@codemirror/commands";
import { indentUnit } from "@codemirror/language";
import { Compartment } from "@codemirror/state";
import { keymap } from "@codemirror/view";
import { basicSetup, EditorView } from "codemirror";
import {
Accessor,
createEffect,
createUniqueId,
JSXElement,
onCleanup,
Setter,
Setter
} from "solid-js";
import CodeView from "../../models/CodeView";
import { getTabIndentationSignal } from "../../options";
import "./codemirror.css";
import languagesMap from "./languages";
export default function CodeMirrorEditor({
code,
setCode,
onInput,
currentLanguage,
form,
setCodeView,
setLabel,
@ -44,7 +41,6 @@ export default function CodeMirrorEditor({
code: Accessor<string>;
setCode: Setter<string>;
onInput(): void;
currentLanguage: Accessor<string>;
form: HTMLFormElement;
setCodeView: Setter<CodeView>;
setLabel: Setter<JSXElement>;
@ -56,7 +52,6 @@ export default function CodeMirrorEditor({
: [];
}
const tabIndentation = new Compartment();
const language = new Compartment();
let avoidChangeNotifications = false;
const labelId = createUniqueId();
const view = new EditorView({
@ -72,8 +67,6 @@ export default function CodeMirrorEditor({
}
}),
EditorView.lineWrapping,
indentUnit.of(" ".repeat(4)),
language.of([]),
],
});
createEffect(() => {
@ -81,13 +74,6 @@ export default function CodeMirrorEditor({
effects: tabIndentation.reconfigure(getTabIndentationExtension()),
});
});
createEffect(async () => {
const callback = languagesMap[currentLanguage()];
const extension = callback ? await callback() : [];
view.dispatch({
effects: language.reconfigure(extension),
});
});
const getValue = () => view.state.doc.toString();
setCodeView({
get code() {

View File

@ -1,110 +0,0 @@
// pastebin.run
// Copyright (C) 2022 Konrad Borowski
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import { StreamLanguage } from "@codemirror/language";
import { Extension } from "@codemirror/state";
const languagesMap: { [name: string]: () => Promise<Extension> } = {
async c() {
return (await import("@codemirror/lang-cpp")).cpp();
},
async cpp() {
return (await import("@codemirror/lang-cpp")).cpp();
},
async csharp() {
return StreamLanguage.define(
(await import("@codemirror/legacy-modes/mode/clike")).csharp
);
},
async go() {
return StreamLanguage.define(
(await import("@codemirror/legacy-modes/mode/go")).go
);
},
async haskell() {
return StreamLanguage.define(
(await import("@codemirror/legacy-modes/mode/haskell")).haskell
);
},
async html() {
return (await import("@codemirror/lang-html")).html();
},
async java() {
return (await import("@codemirror/lang-java")).java();
},
async javascript() {
return (await import("@codemirror/lang-javascript")).javascript();
},
async jinja2() {
return StreamLanguage.define(
(await import("@codemirror/legacy-modes/mode/jinja2")).jinja2
);
},
async jsx() {
return (await import("@codemirror/lang-javascript")).javascript({
jsx: true,
});
},
async markdown() {
return (await import("./markdown")).default;
},
async nix() {
return (await import("@replit/codemirror-lang-nix")).nix();
},
async perl() {
return StreamLanguage.define(
(await import("@codemirror/legacy-modes/mode/perl")).perl
);
},
async php() {
return (await import("@codemirror/lang-php")).php();
},
async postgresql() {
const { sql, PostgreSQL } = await import("@codemirror/lang-sql");
return sql({ dialect: PostgreSQL });
},
async python() {
return (await import("@codemirror/lang-python")).python();
},
async rust() {
return (await import("@codemirror/lang-rust")).rust();
},
async sh() {
return StreamLanguage.define(
(await import("@codemirror/legacy-modes/mode/shell")).shell
);
},
async sql() {
return (await import("@codemirror/lang-sql")).sql();
},
async sqlite() {
const { sql, SQLite } = await import("@codemirror/lang-sql");
return sql({ dialect: SQLite });
},
async typescript() {
return (await import("@codemirror/lang-javascript")).javascript({
typescript: true,
});
},
async tsx() {
return (await import("@codemirror/lang-javascript")).javascript({
jsx: true,
typescript: true,
});
},
};
export default languagesMap;

View File

@ -1,15 +0,0 @@
import { markdown } from "@codemirror/lang-markdown";
import { LanguageDescription } from "@codemirror/language";
import { languages } from "@codemirror/language-data";
export default markdown({
codeLanguages: languages.concat(
LanguageDescription.of({
name: "Nix",
extensions: ["nix"],
async load() {
return (await import("@replit/codemirror-lang-nix")).nix();
},
})
),
});

View File

@ -15,11 +15,10 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import {
Accessor,
createResource,
createSignal,
JSXElement,
Setter,
Setter
} from "solid-js";
import CodeView from "../models/CodeView";
import { getEditorTypeSignal } from "../options";
@ -28,14 +27,12 @@ import TextAreaEditor from "./TextAreaEditor";
export default function Editor({
code: initialCode,
onInput,
currentLanguage,
form,
setCodeView: setCodeView,
setLabel,
}: {
code: string;
onInput(): void;
currentLanguage: Accessor<string>;
form: HTMLFormElement;
setCodeView: Setter<CodeView>;
setLabel: Setter<JSXElement>;
@ -56,7 +53,6 @@ export default function Editor({
code,
setCode,
onInput,
currentLanguage,
form,
setCodeView,
setLabel,

View File

@ -1,48 +0,0 @@
// pastebin.run
// Copyright (C) 2022 Konrad Borowski
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import { For, Setter } from "solid-js";
export default function LanguageSelector({
setCurrentLanguage,
languages,
}: {
setCurrentLanguage: Setter<string>;
languages: HTMLSelectElement;
}) {
return (
<div class="group">
<label>
{"Language: "}
<select
id="language"
name="language"
onChange={(e) =>
setCurrentLanguage(e.currentTarget.selectedOptions[0].value)
}
>
<For each={[...languages.options]}>
{(option) => (
<option value={option.value} selected={option.selected}>
{option.textContent}
</option>
)}
</For>
</select>
</label>
</div>
);
}

View File

@ -1,96 +0,0 @@
// pastebin.run
// Copyright (C) 2022 Konrad Borowski
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import {
createEffect,
createResource,
Match,
onCleanup,
Setter,
Switch,
} from "solid-js";
import CodeView from "../models/CodeView";
import WrapperOptions from "../models/WrapperOptions";
import OutputBox from "./OutputBox";
import Spinner from "./Spinner";
export default function Output(props: {
codeView: CodeView;
stdin: string;
wrapperOptions: WrapperOptions;
setWrapperOptions: Setter<WrapperOptions | undefined>;
}) {
const abortController = new AbortController();
const [output, { refetch }] = createResource(async () => {
const body = new URLSearchParams();
body.append("compilerOptions", props.wrapperOptions.compilerOptions);
body.append("code", props.codeView.code);
body.append("stdin", props.stdin);
return (
await fetch(`/api/v0/run/${props.wrapperOptions.wrapper.identifier}`, {
method: "POST",
body,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
signal: abortController.signal,
})
).json();
});
createEffect(() => {
if (props.wrapperOptions.wrapper.isFormatter && output()?.status === 0) {
props.codeView.code = output().output.replace(
/\x7F(?:E[^\x7F]*|O)?/g,
""
);
if (!output().output.includes("\x7F")) {
props.setWrapperOptions();
}
}
});
onCleanup(() => abortController.abort());
return (
<div id="outputcontainer">
<div id="output">
<Switch
fallback={
<OutputBox
output={output()}
wrapper={props.wrapperOptions.wrapper}
/>
}
>
<Match when={output.loading}>
<Spinner />
</Match>
<Match when={output.error}>
{"An error occured while running the code. "}
<a
href="#"
onClick={(e) => {
e.preventDefault();
refetch();
}}
>
Try again
</a>
.
</Match>
</Switch>
</div>
</div>
);
}

View File

@ -1,91 +0,0 @@
// pastebin.run
// Copyright (C) 2022 Konrad Borowski
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import { For, Show } from "solid-js";
import Wrapper from "../models/Wrapper";
import { createLocalStorageState } from "../options";
function parseOutput(wrapper: Wrapper, output: string) {
return (
<For
each={("O" + output)
.split("\x7F")
.filter(
(x) => x.length > 1 && (!wrapper.isFormatter || !x.startsWith("O"))
)}
fallback={<i>(no output)</i>}
>
{(item) => {
const firstChar = item[0];
if (firstChar === "O") {
return item.substring(1);
}
if (firstChar === "E") {
return <span class="error">{item.substring(1)}</span>;
}
return item;
}}
</For>
);
}
function runAsmFilter(output: string) {
return output.replace(
/(?:\t\.(?:text|file|section|globl|p2align|type|cfi_.*|size|section)\b|.Lfunc_end).*\n?/g,
""
);
}
export default function OutputBox({
output,
wrapper,
}: {
output: { output: string; status: number | null };
wrapper: Wrapper;
}) {
const [filterAsmDirectivesOrig, setFilterAsmDirectives] =
createLocalStorageState("filterAsmDirectives", "true");
const filterAsmDirectives = () => JSON.parse(filterAsmDirectivesOrig());
return (
<>
<div class="stdout-header">
<Show when={output.status}>
<h2>{`Output (exit code ${output.status})`}</h2>
</Show>
<Show when={wrapper.isAsm}>
<label>
<input
type="checkbox"
checked={filterAsmDirectives()}
onChange={(e) =>
setFilterAsmDirectives(e.currentTarget.checked.toString())
}
/>
{" Filter assembler directives"}
</label>
</Show>
</div>
<pre>
{parseOutput(
wrapper,
wrapper.isAsm && filterAsmDirectives()
? runAsmFilter(output.output)
: output.output
)}
</pre>
</>
);
}

View File

@ -1,21 +0,0 @@
// pastebin.run
// Copyright (C) 2022 Konrad Borowski
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import "./spinner.css";
export default function Spinner() {
return <div class="spinner" />;
}

View File

@ -1,18 +0,0 @@
@keyframes spinner {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.spinner {
animation: 1s linear infinite spinner;
border: solid 3px #f5f2f0;
border-bottom-color: #c7c75c;
border-radius: 50%;
width: 50px;
height: 50px;
}

View File

@ -1,37 +0,0 @@
// pastebin.run
// Copyright (C) 2022 Konrad Borowski
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import { Accessor, Setter, Show } from "solid-js";
export default function StandardInput({
visible,
setStandardInput,
}: {
visible: Accessor<boolean>;
setStandardInput: Setter<string>;
}) {
return (
<Show when={visible()}>
<details>
<summary>Standard input</summary>
<textarea
name="stdin"
onInput={(e) => setStandardInput(e.currentTarget.value)}
/>
</details>
</Show>
);
}

View File

@ -1,125 +0,0 @@
// pastebin.run
// Copyright (C) 2022 Konrad Borowski
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import {
Accessor,
createEffect,
createResource,
createSignal,
For,
Setter,
Show,
} from "solid-js";
import CodeView from "../models/CodeView";
import Wrapper from "../models/Wrapper";
import WrapperBarButton from "./WrapperBarButton";
type Language = {
helloWorld: string;
implementations: {
label: string;
wrappers: Wrapper[];
}[];
};
async function fetchLanguage(language: string): Promise<Language> {
return (await fetch(`/api/v0/language/${language}`)).json();
}
export default function WrapperBar({
currentLanguage,
setStandardInputVisible,
codeView,
runEvaluation,
}: {
currentLanguage: Accessor<string>;
setStandardInputVisible: Setter<boolean>;
codeView: Accessor<CodeView>;
runEvaluation: (wrapper: Wrapper, compilerOptions: string) => void;
}) {
const [data] = createResource(currentLanguage, fetchLanguage);
const [currentImplementationIndex, setCurrentImplementationIndex] =
createSignal(0);
let previousHelloWorld = "";
createEffect(() => {
const loadedData = data();
if (loadedData) {
setStandardInputVisible(loadedData.implementations.length > 0);
const code = codeView().code;
if (!code || code === previousHelloWorld) {
previousHelloWorld = codeView().code = loadedData.helloWorld;
} else {
previousHelloWorld = "";
}
}
});
let compilerOptions: HTMLInputElement;
return (
<Show when={data()} keyed>
{(data) => (
<>
<div class="group">
<For
each={
data.implementations[currentImplementationIndex()]?.wrappers
}
>
{(wrapper, index) => (
<>
{" "}
<WrapperBarButton
index={index()}
runEvaluation={() => {
runEvaluation(wrapper, compilerOptions.value);
}}
>
{wrapper.label}
</WrapperBarButton>
</>
)}
</For>
</div>
<Show when={data.implementations.length > 1}>
<div class="group">
<label>
{"Implementation: "}
<select
onChange={(e) =>
setCurrentImplementationIndex(+e.currentTarget.value)
}
>
<For each={data.implementations}>
{({ label }, index) => (
<option value={index()}>{label}</option>
)}
</For>
</select>
</label>
</div>
</Show>
<Show when={data.implementations.length}>
<div class="group">
<label>
{"Compiler options: "}
<input ref={(e) => (compilerOptions = e)} />
</label>
</div>
</Show>
</>
)}
</Show>
);
}

View File

@ -1,48 +0,0 @@
// pastebin.run
// Copyright (C) 2022 Konrad Borowski
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import { onCleanup } from "solid-js";
export default function WrapperBarButton({
index,
runEvaluation,
children,
}: {
index: number;
runEvaluation(): void;
children: string;
}) {
if (index === 0) {
const globalKeyEvent = (e: KeyboardEvent) => {
if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) {
e.preventDefault();
runEvaluation();
}
};
document.addEventListener("keydown", globalKeyEvent);
onCleanup(() => document.removeEventListener("keydown", globalKeyEvent));
}
return (
<button
onClick={(e) => {
e.preventDefault();
runEvaluation();
}}
>
{children}
</button>
);
}

View File

@ -0,0 +1,2 @@
ALTER TABLE pastes DROP COLUMN output;
ALTER TABLE pastes ADD COLUMN stdout text, ADD COLUMN stderr text;

View File

@ -0,0 +1,5 @@
ALTER TABLE pastes
DROP COLUMN language_id,
DROP COLUMN stdin,
DROP COLUMN exit_code,
DROP COLUMN output;

1131
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -7,20 +7,7 @@
},
"dependencies": {
"@codemirror/commands": "^6.2.0",
"@codemirror/lang-cpp": "^6.0.2",
"@codemirror/lang-html": "^6.4.1",
"@codemirror/lang-java": "^6.0.1",
"@codemirror/lang-javascript": "^6.1.3",
"@codemirror/lang-markdown": "^6.0.5",
"@codemirror/lang-php": "^6.0.1",
"@codemirror/lang-python": "^6.1.1",
"@codemirror/lang-rust": "^6.0.1",
"@codemirror/lang-sql": "^6.4.0",
"@codemirror/language": "^6.5.0",
"@codemirror/language-data": "^6.1.0",
"@codemirror/legacy-modes": "^6.3.1",
"@codemirror/view": "^6.8.0",
"@replit/codemirror-lang-nix": "^6.0.0",
"@codemirror/view": "^6.8.1",
"codemirror": "^6.0.1",
"solid-js": "^1.6.10"
},
@ -28,9 +15,9 @@
"@testing-library/jest-dom": "^5.16.5",
"@types/babel__core": "^7.20.0",
"@typescript-eslint/eslint-plugin": "^5.51.0",
"eslint": "^8.33.0",
"eslint": "^8.34.0",
"jsdom": "^21.1.0",
"prettier": "^2.8.3",
"prettier": "^2.8.4",
"solid-testing-library": "^0.5.1",
"typescript": "4.9.5",
"vite": "^4.1.1",

View File

@ -19,23 +19,29 @@ extern crate diesel;
#[macro_use]
extern crate rocket;
mod migration;
mod models;
mod routes;
mod schema;
use crate::routes::{
api_insert_paste, api_language, api_languages, config, display_paste, index, insert_paste,
metrics, raw_paste, run,
api_insert_paste, config, display_paste, index, insert_paste, metrics, raw_paste,
};
use chrono::{Duration, Utc};
use diesel::prelude::*;
use rocket::fairing::AdHoc;
use rocket::http::Header;
use rocket::shield::{Policy, Referrer, Shield};
use rocket::{Build, Rocket};
use rocket_dyn_templates::tera::{self, Value};
use rocket_dyn_templates::Template;
use rocket_sync_db_pools::database;
use std::collections::HashMap;
use std::net::SocketAddr;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::{TcpListener, TcpStream};
use tokio::runtime::Runtime;
use tokio::signal;
use tokio::sync::oneshot::Sender;
#[database("main")]
pub struct Db(PgConnection);
@ -95,8 +101,91 @@ impl Policy for ContentSecurityPolicy {
}
}
#[launch]
fn rocket() -> _ {
fn main() -> Result<(), rocket::Error> {
let webapp = rocket();
let (s, r) = tokio::sync::oneshot::channel();
let runtime = Runtime::new().unwrap();
runtime.spawn(webapp.launch());
runtime.spawn(cli());
runtime.spawn(shutdown_monitor(s));
let handle = runtime.handle().clone();
handle.block_on(async move {
r.await.unwrap();
runtime.shutdown_background();
});
Ok(())
}
async fn shutdown_monitor(s: Sender<i32>) {
tokio::select! {
_ = signal::ctrl_c() => {
s.send(0).unwrap();
},
}
}
async fn cli() {
println!("starting cli");
let addr = "0.0.0.0:9000".parse::<SocketAddr>().unwrap();
let listener = TcpListener::bind(addr).await.unwrap();
let mut db = PgConnection::establish(&std::env::var("POSTGRES_URL").unwrap())
.expect("error in establishing connection with postgresql");
let host = std::env::var("HOST").unwrap();
loop {
let (socket, details) = listener.accept().await.unwrap();
println!("received connection from {:?}", details);
save_paste(&mut db, &host, socket).await.unwrap();
}
}
const BUFFER_SIZE: usize = 128;
async fn save_paste(
db: &mut PgConnection,
host: &str,
mut socket: TcpStream,
) -> Result<(), Box<dyn std::error::Error>> {
let mut paste = vec![];
let mut buffer = [0; BUFFER_SIZE];
loop {
let read = socket.read(&mut buffer).await?;
if read < BUFFER_SIZE {
paste.extend(&buffer[..read]);
break;
}
paste.extend(buffer);
buffer.fill(0);
}
use models::paste;
let v = paste::insert(
db,
// Terminal paste should stay for 3 days
Some(Utc::now() + Duration::days(3)),
&String::from_utf8_lossy(&paste),
)
.unwrap();
let output = format!("{}/{}\n", host, v);
let output: Vec<u8> = output.into_bytes();
socket.write(&output).await?;
socket.flush().await.unwrap();
Ok(())
}
fn rocket() -> Rocket<Build> {
println!("starting rocket");
let mut rocket = rocket::build()
.attach(Template::custom(|engines| {
engines.tera.register_function("js_path", js_path);
@ -109,10 +198,7 @@ fn rocket() -> _ {
Db::get_one(&rocket)
.await
.expect("a database")
.run(|conn| {
diesel_migrations::run_pending_migrations(conn)?;
migration::run(conn)
})
.run(|conn| diesel_migrations::run_pending_migrations(conn))
.await
.expect("database to be migrated");
rocket
@ -125,11 +211,8 @@ fn rocket() -> _ {
.mount(
"/",
routes![
api_language,
api_languages,
api_insert_paste,
config,
run,
index,
insert_paste,
display_paste,

View File

@ -1,160 +0,0 @@
// pastebin.run
// Copyright (C) 2020 Konrad Borowski
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::schema::{implementation_wrappers, implementations, languages};
use diesel::prelude::*;
use diesel::sql_types::{Bool, Integer, Text};
use serde::Deserialize;
use std::error::Error;
use std::fs;
#[derive(Deserialize)]
struct JsonLanguage {
identifier: String,
name: String,
#[serde(default)]
helloworld: String,
#[serde(default)]
implementations: Vec<Implementation>,
}
#[derive(Insertable)]
struct Language<'a> {
identifier: &'a str,
name: String,
priority: i32,
hello_world: &'a str,
}
#[derive(Deserialize)]
struct Implementation {
label: String,
identifier: String,
#[serde(default)]
wrappers: Vec<Wrapper>,
}
#[derive(Deserialize)]
struct Wrapper {
identifier: String,
label: String,
code: String,
#[serde(default)]
is_asm: bool,
#[serde(default)]
is_formatter: bool,
}
pub fn run(connection: &PgConnection) -> Result<(), Box<dyn Error + Send + Sync>> {
let languages: Vec<JsonLanguage> = serde_json::from_slice(&fs::read("languages.json")?)?;
for JsonLanguage {
identifier: languages_identifier,
name,
helloworld: hello_world,
implementations,
} in languages
{
diesel::insert_into(languages::table)
.values(Language {
identifier: &languages_identifier,
name,
priority: 10,
hello_world: &hello_world,
})
.on_conflict(languages::identifier)
.do_update()
.set(languages::hello_world.eq(&hello_world))
.execute(connection)?;
for (
i,
Implementation {
label,
identifier: implementation_identifier,
wrappers,
},
) in (1..).zip(implementations)
{
languages::table
.filter(languages::identifier.eq(&languages_identifier))
.select((
languages::language_id,
label.as_sql::<Text>(),
implementation_identifier.as_sql::<Text>(),
i.as_sql::<Integer>(),
))
.insert_into(implementations::table)
.into_columns((
implementations::language_id,
implementations::label,
implementations::identifier,
implementations::ordering,
))
.on_conflict((implementations::language_id, implementations::identifier))
.do_update()
.set((
implementations::label.eq(&label),
implementations::ordering.eq(i),
))
.execute(connection)?;
for (
i,
Wrapper {
identifier,
label,
code,
is_asm,
is_formatter,
},
) in (1..).zip(wrappers)
{
languages::table
.inner_join(implementations::table)
.filter(languages::identifier.eq(&languages_identifier))
.filter(implementations::identifier.eq(&implementation_identifier))
.select((
implementations::implementation_id,
identifier.as_sql::<Text>(),
label.as_sql::<Text>(),
code.as_sql::<Text>(),
is_asm.as_sql::<Bool>(),
is_formatter.as_sql::<Bool>(),
i.as_sql::<Integer>(),
))
.insert_into(implementation_wrappers::table)
.into_columns((
implementation_wrappers::implementation_id,
implementation_wrappers::identifier,
implementation_wrappers::label,
implementation_wrappers::code,
implementation_wrappers::is_asm,
implementation_wrappers::is_formatter,
implementation_wrappers::ordering,
))
.on_conflict(implementation_wrappers::identifier)
.do_update()
.set((
implementation_wrappers::label.eq(&label),
implementation_wrappers::code.eq(&code),
implementation_wrappers::is_asm.eq(is_asm),
implementation_wrappers::is_formatter.eq(is_formatter),
implementation_wrappers::ordering.eq(i),
))
.execute(connection)?;
}
}
}
Ok(())
}

View File

@ -1,35 +0,0 @@
// pastebin.run
// Copyright (C) 2020-2021 Konrad Borowski
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::schema::languages::dsl::*;
use diesel::prelude::*;
use serde::Serialize;
#[derive(Queryable, Serialize)]
pub struct Language {
pub id: i32,
pub identifier: String,
pub name: String,
}
impl Language {
pub fn fetch(connection: &PgConnection) -> Result<Vec<Language>, diesel::result::Error> {
languages
.select((language_id, identifier, name))
.order((priority.asc(), name.asc()))
.load(connection)
}
}

View File

@ -15,7 +15,6 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
mod cors;
pub mod language;
pub mod paste;
pub use cors::Cors;

View File

@ -14,32 +14,21 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::schema::{languages, pastes};
use ammonia::Builder;
use crate::schema::pastes;
use chrono::{DateTime, Utc};
use diesel::prelude::*;
use itertools::Itertools;
use log::info;
use once_cell::sync::Lazy;
use pulldown_cmark::{Options, Parser};
use rand::seq::SliceRandom;
use rocket::http::Status;
use rocket::response::{self, Debug, Responder};
use rocket::Request;
use serde::Serialize;
use serde_with::skip_serializing_none;
use std::iter;
#[derive(Queryable)]
pub struct Paste {
pub identifier: String,
pub paste: String,
pub language_id: i32,
pub delete_at: Option<DateTime<Utc>>,
pub language_identifier: String,
pub stdin: String,
pub exit_code: Option<i32>,
pub output: Option<String>,
}
impl Paste {
@ -61,23 +50,12 @@ const CHARACTERS: &[u8] = b"23456789bcdfghjkmnpqrstvwxz-";
struct InsertPaste<'a> {
identifier: &'a str,
delete_at: Option<DateTime<Utc>>,
language_id: i32,
paste: &'a str,
stdin: &'a str,
output: Option<&'a str>,
exit_code: Option<i32>,
}
#[derive(Default)]
pub struct ExtraPasteParameters<'a> {
pub stdin: &'a str,
pub output: Option<&'a str>,
pub exit_code: Option<i32>,
}
#[derive(Debug)]
pub enum InsertionError {
Diesel(diesel::result::Error),
UnrecognizedLanguageIdentifier,
}
impl From<diesel::result::Error> for InsertionError {
@ -90,7 +68,6 @@ impl<'r> Responder<'r, 'static> for InsertionError {
fn respond_to(self, request: &'r Request<'_>) -> response::Result<'static> {
match self {
Self::Diesel(e) => Debug(e).respond_to(request),
Self::UnrecognizedLanguageIdentifier => Err(Status::BadRequest),
}
}
}
@ -98,32 +75,17 @@ impl<'r> Responder<'r, 'static> for InsertionError {
pub fn insert(
connection: &PgConnection,
delete_at: Option<DateTime<Utc>>,
language: &str,
paste: &str,
ExtraPasteParameters {
stdin,
output,
exit_code,
}: ExtraPasteParameters,
) -> Result<String, InsertionError> {
let mut rng = rand::thread_rng();
let identifier: String = (0..12)
.map(|_| char::from(*CHARACTERS.choose(&mut rng).expect("a random character")))
.collect();
let language_id = languages::table
.select(languages::language_id)
.filter(languages::identifier.eq(language))
.get_result(connection)
.optional()?
.ok_or(InsertionError::UnrecognizedLanguageIdentifier)?;
let insert_paste = InsertPaste {
identifier: &identifier,
delete_at,
language_id,
paste,
stdin,
output,
exit_code,
};
diesel::insert_into(pastes::table)
.values(&insert_paste)
@ -136,12 +98,7 @@ pub fn insert(
pub struct ExternPaste {
pub identifier: Option<String>,
pub paste: String,
pub language_id: i32,
pub delete_at: Option<String>,
pub markdown: String,
pub stdin: String,
pub exit_code: Option<i32>,
pub output: Option<String>,
}
impl ExternPaste {
@ -149,98 +106,12 @@ impl ExternPaste {
let Paste {
identifier,
paste,
language_id,
language_identifier,
delete_at,
stdin,
exit_code,
output,
} = paste;
let markdown = if language_identifier == "markdown" {
render_markdown(&paste)
} else {
String::new()
};
Self {
identifier: Some(identifier),
paste,
language_id,
delete_at: delete_at.map(|delete_at| delete_at.format("%Y-%m-%d %H:%M").to_string()),
markdown,
stdin,
exit_code,
output,
}
}
}
fn render_markdown(markdown: &str) -> String {
static FILTER: Lazy<Builder<'static>> = Lazy::new(|| {
let mut builder = Builder::new();
builder.link_rel(Some("noopener noreferrer nofollow"));
builder.add_generic_attributes(iter::once("class"));
builder.attribute_filter(|_, attribute, value| {
if attribute == "class" {
// class attribute must have a value that is a set of space-separate tokens
// https://html.spec.whatwg.org/#global-attributes
//
// A set of space-separated tokens is a string containing zero or more words
// (known as tokens) separated by one or more ASCII whitespace, where words
// consist of any string of one or more characters, none of which are ASCII
// whitespace.
// https://html.spec.whatwg.org/#space-separated-tokens
//
// Rust uses the WhatWG Infra Standards definition of ASCII whitespace.
// https://doc.rust-lang.org/stable/std/primitive.char.html#method.is_ascii_whitespace
Some(
value
.split_ascii_whitespace()
.filter(|value| value.starts_with("language-"))
.join(" ")
.into(),
)
} else {
Some(value.into())
}
});
builder
});
let mut output = String::new();
let options = Options::ENABLE_TABLES | Options::ENABLE_STRIKETHROUGH;
pulldown_cmark::html::push_html(&mut output, Parser::new_ext(markdown, options));
FILTER.clean(&output).to_string()
}
#[cfg(test)]
mod test {
use super::render_markdown;
#[test]
fn markdown_works() {
assert_eq!(
render_markdown("**bold**"),
"<p><strong>bold</strong></p>\n"
);
}
#[test]
fn strikethrough_works() {
assert_eq!(render_markdown("~~strike~~"), "<p><del>strike</del></p>\n");
}
#[test]
fn code_blocks_work() {
assert_eq!(
render_markdown("```rust\nfn main() {}\n```"),
"<pre><code class=\"language-rust\">fn main() {}\n</code></pre>\n",
);
}
#[test]
fn only_language_classes_are_allowed() {
assert_eq!(
render_markdown(r#"<br class="language-a madoka language-b homura">"#),
"<br class=\"language-a language-b\">",
);
}
}

View File

@ -14,7 +14,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::models::paste::{self, ExtraPasteParameters, InsertionError};
use crate::models::paste::{self, InsertionError};
use crate::models::Cors;
use crate::Db;
use chrono::Duration;
@ -26,8 +26,6 @@ use std::error::Error;
pub struct PasteForm {
#[field(default = Expiration(None))]
expiration: Expiration,
#[field(default = "plaintext")]
language: String,
code: String,
}
@ -52,13 +50,7 @@ pub async fn api_insert_paste(
paste::insert(
conn,
form.expiration.0.map(|expiration| Utc::now() + expiration),
&form.language,
&form.code,
ExtraPasteParameters {
stdin: "",
output: None,
exit_code: None,
},
)
})
.await?;

View File

@ -1,133 +0,0 @@
// pastebin.run
// Copyright (C) 2020-2021 Konrad Borowski
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::schema::{implementation_wrappers, implementations, languages};
use crate::Db;
use diesel::prelude::*;
use rocket::response::Debug;
use rocket::serde::json::Json;
use serde::Serialize;
#[derive(Queryable)]
struct Language {
id: i32,
hello_world: Option<String>,
}
#[derive(Serialize, Queryable)]
#[serde(rename_all = "camelCase")]
struct Wrapper {
identifier: String,
label: String,
is_asm: bool,
is_formatter: bool,
}
#[derive(Identifiable, Queryable)]
struct Implementation {
id: i32,
label: String,
}
#[derive(Associations, Identifiable, Queryable)]
#[belongs_to(Implementation)]
struct ImplementationWrapper {
id: i32,
implementation_id: i32,
identifier: String,
label: String,
is_asm: bool,
is_formatter: bool,
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct JsonLanguage {
hello_world: Option<String>,
implementations: Vec<JsonImplementation>,
}
#[derive(Serialize)]
struct JsonImplementation {
label: String,
wrappers: Vec<Wrapper>,
}
#[get("/api/v0/language/<identifier>")]
pub async fn api_language(
db: Db,
identifier: String,
) -> Result<Option<Json<JsonLanguage>>, Debug<diesel::result::Error>> {
db.run(|conn| {
let language: Option<Language> = languages::table
.filter(languages::identifier.eq(identifier))
.select((languages::language_id, languages::hello_world))
.get_result(conn)
.optional()?;
Ok(if let Some(language) = language {
let implementations = implementations::table
.select((implementations::implementation_id, implementations::label))
.filter(implementations::language_id.eq(language.id))
.order(implementations::ordering)
.load(conn)?;
let implementation_wrappers = ImplementationWrapper::belonging_to(&implementations)
.select((
implementation_wrappers::implementation_wrapper_id,
implementation_wrappers::implementation_id,
implementation_wrappers::identifier,
implementation_wrappers::label,
implementation_wrappers::is_asm,
implementation_wrappers::is_formatter,
))
.order(implementation_wrappers::ordering)
.load(conn)?;
let implementations = implementation_wrappers
.grouped_by(&implementations)
.into_iter()
.zip(implementations)
.map(|(wrappers, implementation)| JsonImplementation {
label: implementation.label,
wrappers: wrappers
.into_iter()
.map(
|ImplementationWrapper {
identifier,
label,
is_asm,
is_formatter,
..
}| {
Wrapper {
identifier,
label,
is_asm,
is_formatter,
}
},
)
.collect(),
})
.collect();
Some(Json(JsonLanguage {
implementations,
hello_world: language.hello_world,
}))
} else {
None
})
})
.await
}

View File

@ -1,64 +0,0 @@
// pastebin.run
// Copyright (C) 2021 Konrad Borowski
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::models::Cors;
use crate::schema::languages;
use crate::Db;
use diesel::prelude::*;
use rocket::response::Debug;
use rocket::serde::json::Json;
use serde::Serialize;
#[derive(Queryable, Serialize)]
pub struct Language {
identifier: String,
name: String,
}
#[get("/api/v1/languages")]
pub async fn api_languages(
db: Db,
) -> Result<Cors<Json<Vec<Language>>>, Debug<diesel::result::Error>> {
let languages = db
.run(|conn| {
languages::table
.select((languages::identifier, languages::name))
.load(conn)
})
.await?;
Ok(Cors(Json(languages)))
}
#[cfg(all(test, feature = "database_tests"))]
mod test {
use rocket::http::hyper::header::ACCESS_CONTROL_ALLOW_ORIGIN;
use rocket::http::Status;
use rocket::local::blocking::Client;
use rocket::uri;
#[test]
fn test_cors() {
let rocket = Client::untracked(crate::rocket()).unwrap();
let response = rocket.get(uri!(super::api_languages)).dispatch();
assert_eq!(response.status(), Status::Ok);
assert_eq!(
response
.headers()
.get_one(ACCESS_CONTROL_ALLOW_ORIGIN.as_str()),
Some("*"),
);
}
}

View File

@ -14,10 +14,8 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use super::WithTxt;
use crate::models::language::Language;
use crate::models::paste::{ExternPaste, Paste};
use crate::schema::{languages, pastes};
use crate::schema::pastes;
use crate::Db;
use diesel::prelude::*;
use rocket::http::uri::Origin;
@ -27,7 +25,6 @@ use serde::Serialize;
#[derive(Serialize)]
struct DisplayPaste {
languages: Vec<Language>,
description: String,
paste: ExternPaste,
raw_paste_url: Origin<'static>,
@ -40,19 +37,9 @@ pub async fn display_paste(
) -> Result<Option<Template>, Debug<diesel::result::Error>> {
db.run(move |conn| {
Paste::delete_old(conn)?;
let languages = Language::fetch(conn)?;
let paste: Option<Paste> = pastes::table
.inner_join(languages::table.on(pastes::language_id.eq(languages::language_id)))
.select((
pastes::identifier,
pastes::paste,
pastes::language_id,
pastes::delete_at,
languages::identifier,
pastes::stdin,
pastes::exit_code,
pastes::output,
))
.select((pastes::identifier, pastes::paste, pastes::delete_at))
.filter(pastes::identifier.eq(&identifier))
.get_result(conn)
.optional()?;
@ -61,7 +48,6 @@ pub async fn display_paste(
Ok(Some(Template::render(
"display-paste",
&DisplayPaste {
languages,
description,
paste: ExternPaste::from_paste(paste),
raw_paste_url: uri!(super::raw_paste(identifier)),

View File

@ -14,19 +14,15 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::models::language::Language;
use crate::Db;
use rocket::response::Debug;
use rocket_dyn_templates::Template;
use serde::Serialize;
#[derive(Serialize)]
struct Index {
languages: Vec<Language>,
}
struct Index {}
#[get("/")]
pub async fn index(db: Db) -> Result<Template, Debug<diesel::result::Error>> {
let languages = db.run(|conn| Language::fetch(conn)).await?;
Ok(Template::render("index", &Index { languages }))
pub async fn index(_db: Db) -> Result<Template, Debug<diesel::result::Error>> {
Ok(Template::render("index", &Index {}))
}

View File

@ -14,49 +14,56 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::models::paste::{self, ExtraPasteParameters, InsertionError};
use crate::models::paste::{self, InsertionError};
use crate::Db;
use chrono::{Duration, Utc};
use rocket::form::Form;
use rocket::form::{self, Form, FromForm, FromFormField};
use rocket::response::Redirect;
#[derive(FromForm)]
pub struct PasteForm {
language: String,
#[field(validate = len(1..))]
code: String,
share: Share,
#[field(default = "")]
stdin: String,
output: Option<String>,
status: Option<i32>,
duration: ShareDuration,
}
#[derive(FromFormField)]
pub enum Share {
Share,
Share24,
pub enum ShareDuration {
OneHour,
ThreeHour,
TwentyFourHour,
ThreeDays,
SevenDays,
NinetyDays,
}
impl<'v> FromFormField<'v> for ShareDuration {
fn from_value(field: rocket::form::ValueField<'v>) -> rocket::form::Result<'v, Self> {
match field.value {
"1h" => Ok(ShareDuration::OneHour),
"3h" => Ok(ShareDuration::ThreeHour),
"24h" => Ok(ShareDuration::TwentyFourHour),
"3d" => Ok(ShareDuration::ThreeDays),
"7d" => Ok(ShareDuration::SevenDays),
"90d" => Ok(ShareDuration::NinetyDays),
_ => Err(form::Error::validation("invalid value for share duration"))?,
}
}
}
#[post("/", data = "<form>")]
pub async fn insert_paste(db: Db, form: Form<PasteForm>) -> Result<Redirect, InsertionError> {
let delete_at = match form.share {
Share::Share => None,
Share::Share24 => Some(Utc::now() + Duration::hours(24)),
let delete_at = match form.duration {
ShareDuration::OneHour => Some(Utc::now() + Duration::hours(1)),
ShareDuration::ThreeHour => Some(Utc::now() + Duration::hours(3)),
ShareDuration::TwentyFourHour => Some(Utc::now() + Duration::hours(24)),
ShareDuration::ThreeDays => Some(Utc::now() + Duration::days(3)),
ShareDuration::SevenDays => Some(Utc::now() + Duration::days(7)),
ShareDuration::NinetyDays => Some(Utc::now() + Duration::days(90)),
};
let identifier = db
.run(move |conn| {
paste::insert(
conn,
delete_at,
&form.language,
&form.code,
ExtraPasteParameters {
stdin: &form.stdin,
output: form.output.as_deref(),
exit_code: form.status,
},
)
})
.run(move |conn| paste::insert(conn, delete_at, &form.code))
.await?;
Ok(Redirect::to(uri!(super::display_paste(identifier))))
}

View File

@ -15,23 +15,17 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
mod api_insert_paste_route;
mod api_language_route;
mod api_languages_route;
mod config_route;
mod display_paste_route;
mod index_route;
mod insert_paste_route;
mod metrics_route;
mod raw_paste_route;
mod run_route;
pub use api_insert_paste_route::*;
pub use api_language_route::*;
pub use api_languages_route::*;
pub use config_route::*;
pub use display_paste_route::*;
pub use index_route::*;
pub use insert_paste_route::*;
pub use metrics_route::*;
pub use raw_paste_route::*;
pub use run_route::*;

View File

@ -19,45 +19,10 @@ use crate::schema::pastes;
use crate::Db;
use diesel::prelude::*;
use rocket::http::hyper::header::ACCESS_CONTROL_ALLOW_ORIGIN;
use rocket::http::uri::fmt::{Formatter, FromUriParam, Path, UriDisplay};
use rocket::http::{impl_from_uri_param_identity, Header};
use rocket::request::{FromParam, Request};
use rocket::http::Header;
use rocket::request::Request;
use rocket::response::status::NotFound;
use rocket::response::{self, Debug, Responder, Response};
use std::fmt;
pub struct WithTxt(String);
impl<'a> FromParam<'a> for WithTxt {
type Error = &'a str;
fn from_param(param: &str) -> Result<Self, &str> {
if let Some(param) = param.strip_suffix(".txt") {
Ok(WithTxt(
String::from_param(param).unwrap_or_else(|e| match e {}),
))
} else {
Err(param)
}
}
}
impl UriDisplay<Path> for WithTxt {
fn fmt(&self, f: &mut Formatter<Path>) -> fmt::Result {
self.0.fmt(f)?;
f.write_raw(".txt")
}
}
impl_from_uri_param_identity!([Path] WithTxt);
impl FromUriParam<Path, String> for WithTxt {
type Target = WithTxt;
fn from_uri_param(param: String) -> WithTxt {
WithTxt(param)
}
}
pub enum RawPasteResponse {
Found(String),
@ -80,17 +45,17 @@ impl<'r> Responder<'r, 'static> for RawPasteResponse {
}
}
#[get("/<identifier>")]
#[get("/raw/<identifier>")]
pub async fn raw_paste(
db: Db,
identifier: WithTxt,
identifier: String,
) -> Result<RawPasteResponse, Debug<diesel::result::Error>> {
Ok(db
.run(move |conn| {
Paste::delete_old(conn)?;
pastes::table
.select(pastes::paste)
.filter(pastes::identifier.eq(&identifier.0))
.filter(pastes::identifier.eq(&identifier))
.get_result(conn)
.optional()
})

View File

@ -1,106 +0,0 @@
// pastebin.run
// Copyright (C) 2021 Konrad Borowski
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::schema::implementation_wrappers;
use crate::Db;
use diesel::prelude::*;
use once_cell::sync::Lazy;
use reqwest::Client;
use rocket::form::Form;
use rocket::response::Debug;
use rocket::serde::json::Json;
use serde::{Deserialize, Serialize};
use std::env;
use std::error::Error;
static CLIENT: Lazy<Client> = Lazy::new(Client::new);
static SANDBOX_URL: Lazy<String> = Lazy::new(|| env::var("SANDBOX_URL").unwrap());
#[derive(FromForm)]
pub struct RunForm {
code: String,
#[field(name = "compilerOptions")]
compiler_options: String,
stdin: String,
}
#[derive(Serialize)]
struct Request {
files: Files,
stdin: String,
code: String,
}
#[derive(Serialize)]
struct Files {
code: File,
}
#[derive(Serialize)]
struct File {
contents: String,
}
#[derive(Deserialize, Serialize)]
pub struct Output {
status: Option<i32>,
output: String,
}
#[post("/api/v0/run/<identifier>", data = "<form>")]
pub async fn run(
db: Db,
identifier: String,
form: Form<RunForm>,
) -> Result<Option<Json<Output>>, Debug<Box<dyn Error + Send + Sync>>> {
let run = || async {
let language_code = db
.run(|conn| {
implementation_wrappers::table
.filter(implementation_wrappers::identifier.eq(identifier))
.select(implementation_wrappers::code)
.get_result(conn)
.optional()
})
.await?;
let language_code: String = if let Some(code) = language_code {
code
} else {
return Ok(None);
};
let RunForm {
code,
compiler_options,
stdin,
} = form.into_inner();
let json: Output = CLIENT
.post(SANDBOX_URL.as_str())
.json(&Request {
files: Files {
code: File { contents: code },
},
stdin,
code: language_code.replace("%s", &compiler_options),
})
.send()
.await?
.json()
.await?;
Ok(Some(Json(json)))
};
run().await.map_err(Debug)
}

View File

@ -14,39 +14,22 @@ table! {
table! {
implementations (implementation_id) {
implementation_id -> Int4,
language_id -> Int4,
identifier -> Text,
label -> Text,
ordering -> Int4,
}
}
table! {
languages (language_id) {
language_id -> Int4,
priority -> Int4,
name -> Text,
identifier -> Text,
hello_world -> Nullable<Text>,
}
}
table! {
pastes (paste_id) {
paste_id -> Int4,
identifier -> Text,
delete_at -> Nullable<Timestamptz>,
created_at -> Timestamptz,
language_id -> Int4,
paste -> Text,
stdin -> Text,
exit_code -> Nullable<Int4>,
output -> Nullable<Text>,
}
}
joinable!(implementation_wrappers -> implementations (implementation_id));
joinable!(implementations -> languages (language_id));
joinable!(pastes -> languages (language_id));
allow_tables_to_appear_in_same_query!(implementation_wrappers, implementations, languages, pastes,);
allow_tables_to_appear_in_same_query!(implementation_wrappers, implementations, pastes,);

View File

@ -2,9 +2,9 @@
<html lang=en>
<meta charset=utf-8>
<meta name=viewport content="width=device-width, initial-scale=1">
<title>pastebin.run</title>
<title>p.ishan.pw</title>
{{ css_stylesheet() | safe }}
<meta name=description content="{% block description %}Compile and share code in multiple programming languages{% endblock description %}">
<meta name=description content="{% block description %}Compile and share code{% endblock description %}">
<script type=module src="{{ js_path() }}"></script>
<header id=header>
<a href="/">
@ -12,7 +12,8 @@
</a>
<nav id="menu-buttons">
<ul>
<li><a href="https://pastebin.run/api">API</a
<!--TODO: do not hard code -->
<li><a href="https://p.ishan.pw/api">API</a
><li><a href="https://github.com/pastebinrun/pastebinrun/">Source code</a
></ul>
</nav>

View File

@ -3,33 +3,13 @@
{% block description %}{{ description }}{% endblock %}
{% block paste_header %}
<div id=markdown>
{{ paste.markdown | safe }}
</div>
{% if paste.delete_at %}
<div class="autodelete-text">
This paste will be automatically deleted on {{ paste.delete_at }} UTC.
This paste will be automatically deleted on {{ paste.delete_at }} UTC
</div>
{% endif %}
{% if paste.exit_code is defined %}
<input type=hidden id=dbstatus value="{{ paste.exit_code }}">
{% endif %}
{% if paste.output is defined %}
<input type=hidden id=dboutput value="{{ paste.output }}">
{% endif %}
{% endblock %}
{% block code %}{{ paste.paste }}{% endblock %}
{% block languages %}
{% for language in languages %}
<option value="{{ language.identifier }}"
{% if paste.language_id == language.id %}
selected
{% endif %}
>{{ language.name }}</option>
{% endfor %}
{% endblock %}
{% block rawpaste %}<div class="group rawpaste-text"><a href="{{ raw_paste_url }}">raw paste</a></div>{% endblock %}

View File

@ -4,13 +4,6 @@
<form method="post" action="/">
{% block paste_header %}{% endblock %}
<div id="toolbar">
<div class=group><label>Language: <select id=language name=language>
{% block languages %}
{% for language in languages %}
<option value="{{ language.identifier }}">{{ language.name }}</option>
{% endfor %}
{% endblock languages %}
</select></label></div>
{% block rawpaste %}{% endblock %}
<span id="right-buttons">
<button type=submit name=share value=share24>Share (delete after 24 hours)</button>