Merge pull request #7 from StevanFreeborn/stevanfreeborn/feat/add-rust-sdk-and-examples
feat: add rust sdk examples
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
next-env.d.ts
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import nextPlugin from '@next/eslint-plugin-next';
|
||||||
|
import prettierPlugin from 'eslint-plugin-prettier';
|
||||||
|
import prettierConfig from 'eslint-config-prettier';
|
||||||
|
import tsParser from '@typescript-eslint/parser';
|
||||||
|
import tsPlugin from '@typescript-eslint/eslint-plugin';
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
ignores: [
|
||||||
|
'.next/**',
|
||||||
|
'node_modules/**',
|
||||||
|
'coverage/**',
|
||||||
|
'.swc/**',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['**/*.{js,mjs,cjs,ts,tsx}'],
|
||||||
|
languageOptions: {
|
||||||
|
parser: tsParser,
|
||||||
|
parserOptions: {
|
||||||
|
ecmaFeatures: {
|
||||||
|
jsx: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
'@next/next': nextPlugin,
|
||||||
|
'@typescript-eslint': tsPlugin,
|
||||||
|
prettier: prettierPlugin,
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
...nextPlugin.configs.recommended.rules,
|
||||||
|
...nextPlugin.configs['core-web-vitals'].rules,
|
||||||
|
...tsPlugin.configs.recommended.rules,
|
||||||
|
'prettier/prettier': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
endOfLine: 'auto',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
prettierConfig,
|
||||||
|
];
|
||||||
+3
-2
@@ -1,10 +1,11 @@
|
|||||||
import nextJest from 'next/jest';
|
import type { Config } from 'jest';
|
||||||
|
import nextJest from 'next/jest.js';
|
||||||
|
|
||||||
const createJestConfig = nextJest({
|
const createJestConfig = nextJest({
|
||||||
dir: './',
|
dir: './',
|
||||||
});
|
});
|
||||||
|
|
||||||
const config = {
|
const config: Config = {
|
||||||
testEnvironment: 'jest-environment-jsdom',
|
testEnvironment: 'jest-environment-jsdom',
|
||||||
clearMocks: true,
|
clearMocks: true,
|
||||||
collectCoverage: true,
|
collectCoverage: true,
|
||||||
|
|||||||
+1
-5
@@ -1,8 +1,4 @@
|
|||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
const nextConfig = {
|
const nextConfig = {};
|
||||||
experimental: {
|
|
||||||
appDir: true,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = nextConfig;
|
module.exports = nextConfig;
|
||||||
|
|||||||
Generated
+9412
-6335
File diff suppressed because it is too large
Load Diff
+25
-22
@@ -6,33 +6,36 @@
|
|||||||
"dev": "cross-env NODE_OPTIONS='--inspect' next dev",
|
"dev": "cross-env NODE_OPTIONS='--inspect' next dev",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint",
|
"lint": "eslint .",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"test-coverage": "jest && live-server coverage/"
|
"test-coverage": "jest && live-server coverage/"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@markdoc/markdoc": "^0.2.2",
|
"@markdoc/markdoc": "^0.5.7",
|
||||||
"@types/node": "18.15.11",
|
"@types/node": "25.5.0",
|
||||||
"@types/react": "18.0.33",
|
"@types/react": "19.2.14",
|
||||||
"@types/react-dom": "18.0.11",
|
"@types/react-dom": "19.2.3",
|
||||||
"@vercel/analytics": "^1.0.0",
|
"@vercel/analytics": "^2.0.1",
|
||||||
"eslint": "8.38.0",
|
"eslint": "10.1.0",
|
||||||
"eslint-config-next": "13.3.0",
|
"eslint-config-next": "16.2.1",
|
||||||
"next": "13.2.4",
|
"next": "16.2.1",
|
||||||
"prismjs": "^1.29.0",
|
"prismjs": "^1.30.0",
|
||||||
"react": "18.2.0",
|
"react": "19.2.4",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "19.2.4",
|
||||||
"typescript": "5.0.4"
|
"typescript": "6.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@testing-library/jest-dom": "^5.16.5",
|
"@eslint/eslintrc": "^3.3.5",
|
||||||
"@testing-library/react": "^14.0.0",
|
"@testing-library/dom": "^10.4.1",
|
||||||
"@types/prismjs": "^1.26.0",
|
"@testing-library/jest-dom": "^6.9.1",
|
||||||
"cross-env": "^7.0.3",
|
"@testing-library/react": "^16.3.2",
|
||||||
"eslint-plugin-prettier": "^4.2.1",
|
"@types/prismjs": "^1.26.6",
|
||||||
"jest": "^29.5.0",
|
"cross-env": "^10.1.0",
|
||||||
"jest-environment-jsdom": "^29.5.0",
|
"eslint-config-prettier": "^10.1.8",
|
||||||
"prettier": "^2.8.7",
|
"eslint-plugin-prettier": "^5.5.5",
|
||||||
"ts-node": "^10.9.1"
|
"jest": "^30.3.0",
|
||||||
|
"jest-environment-jsdom": "^30.3.0",
|
||||||
|
"prettier": "^3.8.1",
|
||||||
|
"ts-node": "^10.9.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -2,13 +2,24 @@
|
|||||||
|
|
||||||
import React, {
|
import React, {
|
||||||
Children,
|
Children,
|
||||||
MouseEvent,
|
|
||||||
ReactElement,
|
ReactElement,
|
||||||
ReactNode,
|
ReactNode,
|
||||||
useState,
|
useState,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import styles from './CodeSnippet.module.css';
|
import styles from './CodeSnippet.module.css';
|
||||||
|
|
||||||
|
interface MarkdocFenceProps {
|
||||||
|
language?: string;
|
||||||
|
content?: string;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isMarkdocElement(
|
||||||
|
child: ReactNode
|
||||||
|
): child is ReactElement<MarkdocFenceProps> {
|
||||||
|
return React.isValidElement(child);
|
||||||
|
}
|
||||||
|
|
||||||
function CodeSnippetCopyButton({
|
function CodeSnippetCopyButton({
|
||||||
copyText,
|
copyText,
|
||||||
}: {
|
}: {
|
||||||
@@ -20,7 +31,7 @@ function CodeSnippetCopyButton({
|
|||||||
const [showTooltip, setShowTooltip] =
|
const [showTooltip, setShowTooltip] =
|
||||||
useState<boolean>(false);
|
useState<boolean>(false);
|
||||||
|
|
||||||
const handleCopy = (e: MouseEvent<HTMLButtonElement>) => {
|
const handleCopy = () => {
|
||||||
setCopyButtonTooltip(true);
|
setCopyButtonTooltip(true);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setCopyButtonTooltip(false);
|
setCopyButtonTooltip(false);
|
||||||
@@ -129,30 +140,29 @@ export default function CodeSnippet({
|
|||||||
setSelectedLanguage(e.target.value);
|
setSelectedLanguage(e.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const code = Children.toArray(children)
|
const childrenArray = Children.toArray(children);
|
||||||
.filter(
|
|
||||||
child =>
|
|
||||||
React.isValidElement(child) &&
|
|
||||||
child.props.className ===
|
|
||||||
`language-${selectedLanguage}`
|
|
||||||
)
|
|
||||||
.map(child => {
|
|
||||||
return child as ReactElement;
|
|
||||||
})[0];
|
|
||||||
|
|
||||||
const snippetLanguages = Children.toArray(children).map(
|
const code = childrenArray.find(
|
||||||
child => {
|
(child): child is ReactElement<MarkdocFenceProps> => {
|
||||||
if (React.isValidElement(child)) {
|
if (!isMarkdocElement(child)) return false;
|
||||||
const languageClassParts =
|
const { language, className } = child.props;
|
||||||
child.props.className.split('-');
|
return (
|
||||||
if (languageClassParts.length > 1) {
|
language === selectedLanguage ||
|
||||||
const language = languageClassParts[1];
|
className === `language-${selectedLanguage}`
|
||||||
return language;
|
);
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const snippetLanguages = childrenArray
|
||||||
|
.map(child => {
|
||||||
|
if (isMarkdocElement(child)) {
|
||||||
|
const { language, className } = child.props;
|
||||||
|
return language || className?.split('-')[1];
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
})
|
||||||
|
.filter((l): l is string => typeof l === 'string');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.snippetContainer}>
|
<div className={styles.snippetContainer}>
|
||||||
<div className={styles.snippetHeader}>
|
<div className={styles.snippetHeader}>
|
||||||
@@ -176,7 +186,7 @@ export default function CodeSnippet({
|
|||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
<CodeSnippetCopyButton
|
<CodeSnippetCopyButton
|
||||||
copyText={code.props.content}
|
copyText={code?.props.content ?? ''}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'prismjs/components/prism-bash';
|
|||||||
import 'prismjs/components/prism-csharp';
|
import 'prismjs/components/prism-csharp';
|
||||||
import 'prismjs/components/prism-json';
|
import 'prismjs/components/prism-json';
|
||||||
import 'prismjs/components/prism-python';
|
import 'prismjs/components/prism-python';
|
||||||
|
import 'prismjs/components/prism-rust';
|
||||||
import './prism-custom.css';
|
import './prism-custom.css';
|
||||||
|
|
||||||
export default function Fence({
|
export default function Fence({
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { ChangeEvent } from 'react';
|
||||||
import { useSectionContext } from '../context/section';
|
import { useSectionContext } from '../context/section';
|
||||||
import { Doc, DocsStructure } from '../types/types';
|
import { Doc, DocsStructure } from '../types/types';
|
||||||
import { toKebabCase } from '../utils/stringUtils';
|
import { toKebabCase } from '../utils/stringUtils';
|
||||||
@@ -9,7 +10,9 @@ export default function SectionDropdown({
|
|||||||
version: DocsStructure;
|
version: DocsStructure;
|
||||||
}) {
|
}) {
|
||||||
const { section, setSection } = useSectionContext();
|
const { section, setSection } = useSectionContext();
|
||||||
const handleSelectChange = (e: any) => {
|
const handleSelectChange = (
|
||||||
|
e: ChangeEvent<HTMLSelectElement>
|
||||||
|
) => {
|
||||||
setSection(e.target.value);
|
setSection(e.target.value);
|
||||||
location.hash = e.target.value;
|
location.hash = e.target.value;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,7 +7,14 @@ import {
|
|||||||
useState,
|
useState,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
|
|
||||||
const SectionContext = createContext({});
|
type SectionContextType = {
|
||||||
|
section: string;
|
||||||
|
setSection: (section: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const SectionContext = createContext<
|
||||||
|
SectionContextType | undefined
|
||||||
|
>(undefined);
|
||||||
|
|
||||||
export const SectionContextProvider = ({
|
export const SectionContextProvider = ({
|
||||||
children,
|
children,
|
||||||
@@ -25,5 +32,12 @@ export const SectionContextProvider = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useSectionContext: any = () =>
|
export const useSectionContext = (): SectionContextType => {
|
||||||
useContext(SectionContext);
|
const context = useContext(SectionContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error(
|
||||||
|
'useSectionContext must be used within SectionContextProvider'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
};
|
||||||
|
|||||||
@@ -7,6 +7,11 @@ import {
|
|||||||
useState,
|
useState,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
|
|
||||||
|
interface ThemeContextType {
|
||||||
|
theme: string;
|
||||||
|
setTheme: (theme: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
const darkTheme = {
|
const darkTheme = {
|
||||||
color: 'white',
|
color: 'white',
|
||||||
backgroundColor: '#05254a',
|
backgroundColor: '#05254a',
|
||||||
@@ -17,7 +22,9 @@ const lightTheme = {
|
|||||||
backgroundColor: 'white',
|
backgroundColor: 'white',
|
||||||
};
|
};
|
||||||
|
|
||||||
const ThemeContext = createContext({});
|
const ThemeContext = createContext<
|
||||||
|
ThemeContextType | undefined
|
||||||
|
>(undefined);
|
||||||
|
|
||||||
export const ThemeContextProvider = ({
|
export const ThemeContextProvider = ({
|
||||||
children,
|
children,
|
||||||
@@ -38,5 +45,12 @@ export const themes = {
|
|||||||
light: lightTheme,
|
light: lightTheme,
|
||||||
} as { [key: string]: { [key: string]: string } };
|
} as { [key: string]: { [key: string]: string } };
|
||||||
|
|
||||||
export const useThemeContext: any = () =>
|
export const useThemeContext = (): ThemeContextType => {
|
||||||
useContext(ThemeContext);
|
const context = useContext(ThemeContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error(
|
||||||
|
'useThemeContext must be used within ThemeContextProvider'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
};
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ export default function GlobalError({
|
|||||||
error: Error;
|
error: Error;
|
||||||
reset: () => void;
|
reset: () => void;
|
||||||
}) {
|
}) {
|
||||||
|
console.error(error);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<h2>Something went wrong!</h2>
|
<h2>Something went wrong!</h2>
|
||||||
|
|||||||
+3
-2
@@ -15,11 +15,12 @@ export const metadata = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
viewport: {
|
};
|
||||||
|
|
||||||
|
export const viewport = {
|
||||||
width: 'device-width',
|
width: 'device-width',
|
||||||
initialScale: 1,
|
initialScale: 1,
|
||||||
maximumScale: 1,
|
maximumScale: 1,
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({
|
||||||
|
|||||||
+15
-3
@@ -83,7 +83,7 @@ function parseMarkdown(sourcePath: string): ReactNode {
|
|||||||
const ast = Markdoc.parse(source);
|
const ast = Markdoc.parse(source);
|
||||||
const content = Markdoc.transform(
|
const content = Markdoc.transform(
|
||||||
ast,
|
ast,
|
||||||
markDocConfig as any
|
markDocConfig as never
|
||||||
);
|
);
|
||||||
const children = Markdoc.renderers.react(content, React, {
|
const children = Markdoc.renderers.react(content, React, {
|
||||||
components: {
|
components: {
|
||||||
@@ -102,7 +102,13 @@ function getCopyChild(
|
|||||||
docPath: string
|
docPath: string
|
||||||
) {
|
) {
|
||||||
const copyPath = getCopyPath(version, doc, docPath);
|
const copyPath = getCopyPath(version, doc, docPath);
|
||||||
return parseMarkdown(copyPath);
|
const child = parseMarkdown(copyPath);
|
||||||
|
|
||||||
|
if (React.isValidElement(child)) {
|
||||||
|
return React.cloneElement(child, { key: 'copy' });
|
||||||
|
}
|
||||||
|
|
||||||
|
return child;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getExampleChild(
|
function getExampleChild(
|
||||||
@@ -111,7 +117,13 @@ function getExampleChild(
|
|||||||
docPath: string
|
docPath: string
|
||||||
) {
|
) {
|
||||||
const examplePath = getExamplePath(version, doc, docPath);
|
const examplePath = getExamplePath(version, doc, docPath);
|
||||||
return parseMarkdown(examplePath);
|
const child = parseMarkdown(examplePath);
|
||||||
|
|
||||||
|
if (React.isValidElement(child)) {
|
||||||
|
return React.cloneElement(child, { key: 'example' });
|
||||||
|
}
|
||||||
|
|
||||||
|
return child;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function loadSections(
|
export function loadSections(
|
||||||
|
|||||||
@@ -56,6 +56,23 @@ print(f'Name: {response.data.app.name}')
|
|||||||
print(f'href: {response.data.app.href}')
|
print(f'href: {response.data.app.href}')
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use onspring::OnspringClient;
|
||||||
|
|
||||||
|
let client = OnspringClient::builder(
|
||||||
|
"000000ffffff000000ffffff/00000000-ffff-0000-ffff-000000000000"
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let app = client.get_app(195).await?;
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"{}, {}",
|
||||||
|
app.id,
|
||||||
|
app.name.unwrap_or_default()
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
{% /code %}
|
{% /code %}
|
||||||
|
|
||||||
{% code heading="RESPONSE" defaultLanguage="json" %}
|
{% code heading="RESPONSE" defaultLanguage="json" %}
|
||||||
|
|||||||
@@ -66,6 +66,30 @@ for app in response.data.apps:
|
|||||||
print(f'href: {app.href}')
|
print(f'href: {app.href}')
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use onspring::{OnspringClient, PagingRequest};
|
||||||
|
|
||||||
|
let client = OnspringClient::builder(
|
||||||
|
"000000ffffff000000ffffff/00000000-ffff-0000-ffff-000000000000"
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let paging = PagingRequest {
|
||||||
|
page_number: 1,
|
||||||
|
page_size: 10,
|
||||||
|
};
|
||||||
|
|
||||||
|
let response = client.list_apps(Some(paging)).await?;
|
||||||
|
|
||||||
|
for app in response.items.unwrap_or_default() {
|
||||||
|
println!(
|
||||||
|
"{}, {}",
|
||||||
|
app.id,
|
||||||
|
app.name.unwrap_or_default()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
{% /code %}
|
{% /code %}
|
||||||
|
|
||||||
{% code heading="RESPONSE" defaultLanguage="json" %}
|
{% code heading="RESPONSE" defaultLanguage="json" %}
|
||||||
|
|||||||
@@ -67,6 +67,26 @@ for app in response.data.apps:
|
|||||||
print(f'href: {app.href}')
|
print(f'href: {app.href}')
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use onspring::OnspringClient;
|
||||||
|
|
||||||
|
let client = OnspringClient::builder(
|
||||||
|
"000000ffffff000000ffffff/00000000-ffff-0000-ffff-000000000000"
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let ids = vec![8, 79, 137, 193, 183];
|
||||||
|
let response = client.batch_get_apps(&ids).await?;
|
||||||
|
|
||||||
|
for app in response.items.unwrap_or_default() {
|
||||||
|
println!(
|
||||||
|
"{}, {}",
|
||||||
|
app.id,
|
||||||
|
app.name.unwrap_or_default()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
{% /code %}
|
{% /code %}
|
||||||
|
|
||||||
{% code heading="RESPONSE" defaultLanguage="json" %}
|
{% code heading="RESPONSE" defaultLanguage="json" %}
|
||||||
|
|||||||
@@ -56,4 +56,16 @@ else:
|
|||||||
print('Attempt to connect failed')
|
print('Attempt to connect failed')
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use onspring::OnspringClient;
|
||||||
|
|
||||||
|
let client = OnspringClient::builder(
|
||||||
|
"000000ffffff000000ffffff/00000000-ffff-0000-ffff-000000000000"
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
client.ping().await?;
|
||||||
|
println!("Connected to the Onspring API successfully.");
|
||||||
|
```
|
||||||
|
|
||||||
{% /code %}
|
{% /code %}
|
||||||
|
|||||||
@@ -54,6 +54,19 @@ print(f'Status Code: {response.statusCode}')
|
|||||||
print(f'Field Id: {response.data.id}')
|
print(f'Field Id: {response.data.id}')
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use onspring::OnspringClient;
|
||||||
|
|
||||||
|
let client = OnspringClient::builder(
|
||||||
|
"000000ffffff000000ffffff/00000000-ffff-0000-ffff-000000000000"
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let field = client.get_field(6986).await?;
|
||||||
|
|
||||||
|
println!("Field Id: {}", field.id);
|
||||||
|
```
|
||||||
|
|
||||||
{% /code %}
|
{% /code %}
|
||||||
|
|
||||||
{% code heading="RESPONSE" defaultLanguage="json" %}
|
{% code heading="RESPONSE" defaultLanguage="json" %}
|
||||||
|
|||||||
@@ -64,6 +64,26 @@ for field in response.data.fields:
|
|||||||
print(f'Field Id: {field.id}')
|
print(f'Field Id: {field.id}')
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use onspring::{OnspringClient, PagingRequest};
|
||||||
|
|
||||||
|
let client = OnspringClient::builder(
|
||||||
|
"000000ffffff000000ffffff/00000000-ffff-0000-ffff-000000000000"
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let paging = PagingRequest {
|
||||||
|
page_number: 1,
|
||||||
|
page_size: 10,
|
||||||
|
};
|
||||||
|
|
||||||
|
let response = client.list_fields(195, Some(paging)).await?;
|
||||||
|
|
||||||
|
for field in response.items.unwrap_or_default() {
|
||||||
|
println!("Field Id: {}", field.id);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
{% /code %}
|
{% /code %}
|
||||||
|
|
||||||
{% code heading="RESPONSE" defaultLanguage="json" %}
|
{% code heading="RESPONSE" defaultLanguage="json" %}
|
||||||
|
|||||||
@@ -63,6 +63,22 @@ for field in response.data.fields:
|
|||||||
print(f'Field Id: {field.id}')
|
print(f'Field Id: {field.id}')
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use onspring::OnspringClient;
|
||||||
|
|
||||||
|
let client = OnspringClient::builder(
|
||||||
|
"000000ffffff000000ffffff/00000000-ffff-0000-ffff-000000000000"
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let ids = vec![6983, 6986, 6987, 6985, 6984];
|
||||||
|
let response = client.batch_get_fields(&ids).await?;
|
||||||
|
|
||||||
|
for field in response.items.unwrap_or_default() {
|
||||||
|
println!("Field Id: {}", field.id);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
{% /code %}
|
{% /code %}
|
||||||
|
|
||||||
{% code heading="RESPONSE" defaultLanguage="json" %}
|
{% code heading="RESPONSE" defaultLanguage="json" %}
|
||||||
|
|||||||
@@ -96,6 +96,31 @@ print(f'Status Code: {response.statusCode}')
|
|||||||
print(f'File Id: {response.data.id}')
|
print(f'File Id: {response.data.id}')
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use onspring::{OnspringClient, SaveFileRequest};
|
||||||
|
|
||||||
|
let client = OnspringClient::builder(
|
||||||
|
"000000ffffff000000ffffff/00000000-ffff-0000-ffff-000000000000"
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let request = SaveFileRequest {
|
||||||
|
record_id: 140,
|
||||||
|
field_id: 6989,
|
||||||
|
notes: Some(
|
||||||
|
"Updating record with attachment.".to_string()
|
||||||
|
),
|
||||||
|
modified_date: Some(chrono::Utc::now()),
|
||||||
|
file_name: "test-attachment.txt".to_string(),
|
||||||
|
file_data: std::fs::read("test-attachment.txt")?,
|
||||||
|
content_type: "text/plain".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let response = client.upload_file(request).await?;
|
||||||
|
|
||||||
|
println!("New File Id is: {}", response.id);
|
||||||
|
```
|
||||||
|
|
||||||
{% /code %}
|
{% /code %}
|
||||||
|
|
||||||
{% code heading="RESPONSE" defaultLanguage="json" %}
|
{% code heading="RESPONSE" defaultLanguage="json" %}
|
||||||
|
|||||||
@@ -60,4 +60,17 @@ else:
|
|||||||
print('Error deleting file')
|
print('Error deleting file')
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use onspring::OnspringClient;
|
||||||
|
|
||||||
|
let client = OnspringClient::builder(
|
||||||
|
"000000ffffff000000ffffff/00000000-ffff-0000-ffff-000000000000"
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
client.delete_file(140, 6989, 1904).await?;
|
||||||
|
|
||||||
|
println!("File deleted successfully");
|
||||||
|
```
|
||||||
|
|
||||||
{% /code %}
|
{% /code %}
|
||||||
|
|||||||
@@ -76,6 +76,25 @@ with open(filePath, "wb") as file:
|
|||||||
print(f'File Location: {filePath}')
|
print(f'File Location: {filePath}')
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use onspring::OnspringClient;
|
||||||
|
|
||||||
|
let client = OnspringClient::builder(
|
||||||
|
"000000ffffff000000ffffff/00000000-ffff-0000-ffff-000000000000"
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let file = client
|
||||||
|
.get_file(1, 6989, 89)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
println!("File Name: {:?}", file.file_name);
|
||||||
|
println!("Content Type: {:?}", file.content_type);
|
||||||
|
println!("Size: {} bytes", file.data.len());
|
||||||
|
|
||||||
|
std::fs::write(&file.file_name, &file.data)?;
|
||||||
|
```
|
||||||
|
|
||||||
{% /code %}
|
{% /code %}
|
||||||
|
|
||||||
{% code heading="RESPONSE" defaultLanguage="text" %}
|
{% code heading="RESPONSE" defaultLanguage="text" %}
|
||||||
|
|||||||
@@ -53,6 +53,22 @@ print(f'Name: {response.data.fileInfo.name}')
|
|||||||
print(f'Content Type: {response.data.fileInfo.contentType}')
|
print(f'Content Type: {response.data.fileInfo.contentType}')
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use onspring::OnspringClient;
|
||||||
|
|
||||||
|
let client = OnspringClient::builder(
|
||||||
|
"000000ffffff000000ffffff/00000000-ffff-0000-ffff-000000000000"
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let info = client
|
||||||
|
.get_file_info(1, 6989, 89)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
println!("Name: {:?}", info.name);
|
||||||
|
println!("Content Type: {:?}", info.content_type);
|
||||||
|
```
|
||||||
|
|
||||||
{% /code %}
|
{% /code %}
|
||||||
|
|
||||||
{% code heading="RESPONSE" defaultLanguage="json" %}
|
{% code heading="RESPONSE" defaultLanguage="json" %}
|
||||||
|
|||||||
@@ -67,4 +67,22 @@ print(f'Status Code: {response.statusCode}')
|
|||||||
print(f'Message: {response.message}')
|
print(f'Message: {response.message}')
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use onspring::OnspringClient;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
let client = OnspringClient::builder(
|
||||||
|
"000000ffffff000000ffffff/00000000-ffff-0000-ffff-000000000000"
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let item_id = Uuid::parse_str(
|
||||||
|
"e3371dbf-b557-4a31-b32f-33c0265d2a59"
|
||||||
|
)?;
|
||||||
|
|
||||||
|
client.delete_list_item(906, item_id).await?;
|
||||||
|
|
||||||
|
println!("List item deleted");
|
||||||
|
```
|
||||||
|
|
||||||
{% /code %}
|
{% /code %}
|
||||||
|
|||||||
@@ -89,6 +89,28 @@ print(f'Status Code: {response.statusCode}')
|
|||||||
print(f'Id: {response.data.id}')
|
print(f'Id: {response.data.id}')
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use onspring::{OnspringClient, SaveListItemRequest};
|
||||||
|
|
||||||
|
let client = OnspringClient::builder(
|
||||||
|
"000000ffffff000000ffffff/00000000-ffff-0000-ffff-000000000000"
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let request = SaveListItemRequest {
|
||||||
|
id: None,
|
||||||
|
name: "Not Started".to_string(),
|
||||||
|
numeric_value: Some(0.0),
|
||||||
|
color: Some("#ffffff".to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let response = client
|
||||||
|
.save_list_item(906, request)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
println!("Id: {}", response.id);
|
||||||
|
```
|
||||||
|
|
||||||
{% /code %}
|
{% /code %}
|
||||||
|
|
||||||
{% code heading="RESPONSE" defaultLanguage="json" %}
|
{% code heading="RESPONSE" defaultLanguage="json" %}
|
||||||
|
|||||||
@@ -58,4 +58,17 @@ print(f'Status Code: {response.statusCode}')
|
|||||||
print(f'Message: {response.message}')
|
print(f'Message: {response.message}')
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use onspring::OnspringClient;
|
||||||
|
|
||||||
|
let client = OnspringClient::builder(
|
||||||
|
"000000ffffff000000ffffff/00000000-ffff-0000-ffff-000000000000"
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
client.delete_record(195, 140).await?;
|
||||||
|
|
||||||
|
println!("Record deleted");
|
||||||
|
```
|
||||||
|
|
||||||
{% /code %}
|
{% /code %}
|
||||||
|
|||||||
@@ -73,4 +73,22 @@ print(f'Status Code: {response.statusCode}')
|
|||||||
print(f'Message: {response.message}')
|
print(f'Message: {response.message}')
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use onspring::{OnspringClient, BatchDeleteRecordsRequest};
|
||||||
|
|
||||||
|
let client = OnspringClient::builder(
|
||||||
|
"000000ffffff000000ffffff/00000000-ffff-0000-ffff-000000000000"
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let request = BatchDeleteRecordsRequest {
|
||||||
|
app_id: 195,
|
||||||
|
record_ids: vec![138, 139],
|
||||||
|
};
|
||||||
|
|
||||||
|
client.batch_delete_records(request).await?;
|
||||||
|
|
||||||
|
println!("Records deleted successfully");
|
||||||
|
```
|
||||||
|
|
||||||
{% /code %}
|
{% /code %}
|
||||||
|
|||||||
@@ -70,6 +70,36 @@ for field in response.data.fields:
|
|||||||
print(f'Value: {field.GetResultValueString()}')
|
print(f'Value: {field.GetResultValueString()}')
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use onspring::OnspringClient;
|
||||||
|
|
||||||
|
let client = OnspringClient::builder(
|
||||||
|
"000000ffffff000000ffffff/00000000-ffff-0000-ffff-000000000000"
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let record = client
|
||||||
|
.get_record(195, 1, None, None)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"AppId: {}, RecordId: {}",
|
||||||
|
record.app_id,
|
||||||
|
record.record_id
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some(field_data) = record.field_data {
|
||||||
|
for field in field_data {
|
||||||
|
println!(
|
||||||
|
"FieldId: {}, Type: {:?}, Value: {}",
|
||||||
|
field.field_id,
|
||||||
|
field.value_type,
|
||||||
|
field.value
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
{% /code %}
|
{% /code %}
|
||||||
|
|
||||||
{% code heading="RESPONSE" defaultLanguage="json" %}
|
{% code heading="RESPONSE" defaultLanguage="json" %}
|
||||||
|
|||||||
@@ -71,6 +71,32 @@ for record in response.data.records:
|
|||||||
print(f'Value: {field.GetResultValueString()}')
|
print(f'Value: {field.GetResultValueString()}')
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use onspring::{OnspringClient, PagingRequest};
|
||||||
|
|
||||||
|
let client = OnspringClient::builder(
|
||||||
|
"000000ffffff000000ffffff/00000000-ffff-0000-ffff-000000000000"
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let paging = PagingRequest {
|
||||||
|
page_number: 1,
|
||||||
|
page_size: 10,
|
||||||
|
};
|
||||||
|
|
||||||
|
let response = client
|
||||||
|
.list_records(195, Some(paging), None, None)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
for record in response.items.unwrap_or_default() {
|
||||||
|
println!(
|
||||||
|
"AppId: {}, RecordId: {}",
|
||||||
|
record.app_id,
|
||||||
|
record.record_id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
{% /code %}
|
{% /code %}
|
||||||
|
|
||||||
{% code heading="RESPONSE" defaultLanguage="json" %}
|
{% code heading="RESPONSE" defaultLanguage="json" %}
|
||||||
|
|||||||
@@ -100,6 +100,34 @@ for record in response.data.records:
|
|||||||
print(f'Value: {field.GetResultValueString()}')
|
print(f'Value: {field.GetResultValueString()}')
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use onspring::{OnspringClient, BatchGetRecordsRequest, DataFormat};
|
||||||
|
|
||||||
|
let client = OnspringClient::builder(
|
||||||
|
"000000ffffff000000ffffff/00000000-ffff-0000-ffff-000000000000"
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let request = BatchGetRecordsRequest {
|
||||||
|
app_id: 195,
|
||||||
|
record_ids: vec![1],
|
||||||
|
field_ids: Some(vec![6983, 6986, 6987, 6985, 6984]),
|
||||||
|
data_format: Some(DataFormat::Raw),
|
||||||
|
};
|
||||||
|
|
||||||
|
let response = client
|
||||||
|
.batch_get_records(request)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
for record in response.items.unwrap_or_default() {
|
||||||
|
println!(
|
||||||
|
"AppId: {}, RecordId: {}",
|
||||||
|
record.app_id,
|
||||||
|
record.record_id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
{% /code %}
|
{% /code %}
|
||||||
|
|
||||||
{% code heading="RESPONSE" defaultLanguage="json" %}
|
{% code heading="RESPONSE" defaultLanguage="json" %}
|
||||||
|
|||||||
@@ -126,6 +126,34 @@ for record in response.data.records:
|
|||||||
print(f'Value: {field.GetResultValueString()}')
|
print(f'Value: {field.GetResultValueString()}')
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use onspring::{OnspringClient, QueryRecordsRequest, DataFormat};
|
||||||
|
|
||||||
|
let client = OnspringClient::builder(
|
||||||
|
"000000ffffff000000ffffff/00000000-ffff-0000-ffff-000000000000"
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let query = QueryRecordsRequest {
|
||||||
|
app_id: 195,
|
||||||
|
filter: "6983 eq 'Test Task 5'".to_string(),
|
||||||
|
field_ids: Some(vec![6983, 6986, 6987, 6985, 6984]),
|
||||||
|
data_format: Some(DataFormat::Formatted),
|
||||||
|
};
|
||||||
|
|
||||||
|
let response = client
|
||||||
|
.query_records(query, None)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
for record in response.items.unwrap_or_default() {
|
||||||
|
println!(
|
||||||
|
"AppId: {}, RecordId: {}",
|
||||||
|
record.app_id,
|
||||||
|
record.record_id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
{% /code %}
|
{% /code %}
|
||||||
|
|
||||||
{% code heading="RESPONSE" defaultLanguage="json" %}
|
{% code heading="RESPONSE" defaultLanguage="json" %}
|
||||||
|
|||||||
@@ -123,6 +123,48 @@ for warning in response.data.warnings:
|
|||||||
print(f'Warning: {warning}')
|
print(f'Warning: {warning}')
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use onspring::{OnspringClient, SaveRecordRequest};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
let client = OnspringClient::builder(
|
||||||
|
"000000ffffff000000ffffff/00000000-ffff-0000-ffff-000000000000"
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let mut fields = HashMap::new();
|
||||||
|
fields.insert(
|
||||||
|
"6983".to_string(),
|
||||||
|
serde_json::json!("A New Test Task"),
|
||||||
|
);
|
||||||
|
fields.insert(
|
||||||
|
"6984".to_string(),
|
||||||
|
serde_json::json!("This is a test task."),
|
||||||
|
);
|
||||||
|
fields.insert(
|
||||||
|
"6985".to_string(),
|
||||||
|
serde_json::json!("12/25/2021"),
|
||||||
|
);
|
||||||
|
fields.insert(
|
||||||
|
"6986".to_string(),
|
||||||
|
serde_json::json!("4118d53a-9121-4345-8682-07f23d606daa"),
|
||||||
|
);
|
||||||
|
fields.insert(
|
||||||
|
"6987".to_string(),
|
||||||
|
serde_json::json!([4]),
|
||||||
|
);
|
||||||
|
|
||||||
|
let request = SaveRecordRequest {
|
||||||
|
app_id: 195,
|
||||||
|
record_id: None,
|
||||||
|
fields,
|
||||||
|
};
|
||||||
|
|
||||||
|
let response = client.save_record(request).await?;
|
||||||
|
|
||||||
|
println!("New Record Id is: {}", response.id);
|
||||||
|
```
|
||||||
|
|
||||||
{% /code %}
|
{% /code %}
|
||||||
|
|
||||||
{% code heading="Response" defaultLanguage="json" %}
|
{% code heading="Response" defaultLanguage="json" %}
|
||||||
|
|||||||
@@ -64,6 +64,31 @@ for row in response.data.rows:
|
|||||||
print(f'Record Id {row.recordId}: {", ".join([str(cell) for cell in row.cells])}')
|
print(f'Record Id {row.recordId}: {", ".join([str(cell) for cell in row.cells])}')
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use onspring::OnspringClient;
|
||||||
|
|
||||||
|
let client = OnspringClient::builder(
|
||||||
|
"000000ffffff000000ffffff/00000000-ffff-0000-ffff-000000000000"
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let data = client
|
||||||
|
.get_report(613, None, None)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
println!("Columns: {:?}", data.columns);
|
||||||
|
|
||||||
|
if let Some(rows) = data.rows {
|
||||||
|
for row in rows {
|
||||||
|
println!(
|
||||||
|
"Record Id {:?}: {:?}",
|
||||||
|
row.record_id,
|
||||||
|
row.cells
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
{% /code %}
|
{% /code %}
|
||||||
|
|
||||||
{% code heading="RESPONSE" defaultLanguage="json" %}
|
{% code heading="RESPONSE" defaultLanguage="json" %}
|
||||||
|
|||||||
@@ -64,6 +64,33 @@ for report in response.data.reports:
|
|||||||
print(f' Description: {report.description}')
|
print(f' Description: {report.description}')
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use onspring::{OnspringClient, PagingRequest};
|
||||||
|
|
||||||
|
let client = OnspringClient::builder(
|
||||||
|
"000000ffffff000000ffffff/00000000-ffff-0000-ffff-000000000000"
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let paging = PagingRequest {
|
||||||
|
page_number: 1,
|
||||||
|
page_size: 10,
|
||||||
|
};
|
||||||
|
|
||||||
|
let response = client
|
||||||
|
.list_reports(195, Some(paging))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
for report in response.items.unwrap_or_default() {
|
||||||
|
println!(
|
||||||
|
"{}, {}, {}",
|
||||||
|
report.id,
|
||||||
|
report.app_id,
|
||||||
|
report.name.unwrap_or_default()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
{% /code %}
|
{% /code %}
|
||||||
|
|
||||||
{% code heading="RESPONSE" defaultLanguage="json" %}
|
{% code heading="RESPONSE" defaultLanguage="json" %}
|
||||||
|
|||||||
+21
-7
@@ -1,7 +1,11 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es5",
|
"target": "esnext",
|
||||||
"lib": ["dom", "dom.iterable", "esnext"],
|
"lib": [
|
||||||
|
"dom",
|
||||||
|
"dom.iterable",
|
||||||
|
"esnext"
|
||||||
|
],
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
@@ -9,10 +13,10 @@
|
|||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"module": "esnext",
|
"module": "esnext",
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "bundler",
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"jsx": "preserve",
|
"jsx": "react-jsx",
|
||||||
"incremental": true,
|
"incremental": true,
|
||||||
"plugins": [
|
"plugins": [
|
||||||
{
|
{
|
||||||
@@ -20,9 +24,19 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*"]
|
"@/*": [
|
||||||
|
"./src/*"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
"include": [
|
||||||
"exclude": ["node_modules"]
|
"next-env.d.ts",
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.tsx",
|
||||||
|
".next/types/**/*.ts",
|
||||||
|
".next/dev/types/**/*.ts"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user