├── .github
└── FUNDING.yml
├── .gitignore
├── READMe.md
├── components
├── CodePart.js
└── nav.js
├── helpers
├── index.js
└── useLocalStorage.js
├── next.config.js
├── package-lock.json
├── package.json
├── pages
├── index.js
└── style.css
├── preview.png
├── public
├── cover.png
└── favicon.ico
└── yarn.lock
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | custom: ["https://sendacoin.to/devzstudio"]
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | .env*
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | .now
--------------------------------------------------------------------------------
/READMe.md:
--------------------------------------------------------------------------------
1 | # SQL to Graphql Schema Generator
2 |
3 | ⚛️ Generate GraphQL Scheme Online From SQL Query
4 |
5 | []()
6 |
7 | Check live on https://sql-to-graphql.now.sh/
8 |
9 |
10 | Made with ❤️ by
11 |
12 |
13 |
14 | ## ✨ Features
15 |
16 | - Convert SQL Table To GraphQL Schema
17 |
18 | - Copy Generated Code to Clipboard
19 |
20 | - Dark Mode
21 |
22 | ## 🗒 Docs
23 |
24 | - Paste SQL Query example.
25 |
26 | ```
27 | CREATE TABLE `users` (
28 | `id` int(11) NOT NULL AUTO_INCREMENT,
29 | `username` varchar(225) NOT NULL,
30 | `password` varchar(225) NOT NULL,
31 | `email` varchar(225) NOT NULL,
32 | `avatar` varchar(225) NOT NULL,
33 | PRIMARY KEY (`id`),
34 | UNIQUE KEY `url` (`url`)
35 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
36 | ```
37 |
38 | - Click on the Run Button
39 |
40 | ## 🔌 Usage
41 |
42 | - Try Online : https://sql-to-graphql.now.sh/
43 |
44 | - Or Clone the repo
45 |
46 | ```
47 | git clone https://github.com/Devzstudio/SQL-to-Graphql-Schema-Generator.git
48 | ```
49 |
50 | - Install dependencies and start
51 |
52 | ```
53 | npm install
54 | ```
55 |
56 | ```
57 | npm run dev
58 | ```
59 |
60 | ## 🛣 ToDo
61 |
62 | - Add Keyboard Shortcut for Paste and Copy
63 |
64 | ## 🤝 Contributing
65 |
66 | Contributions, issues and feature requests are welcome! 😍
67 |
68 | ## Show your support
69 |
70 | Give a ⭐️ if this project helped you! 🥰
71 |
--------------------------------------------------------------------------------
/components/CodePart.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { UnControlled as CodeMirror } from 'react-codemirror2';
3 |
4 | const CodePart = ({ value, onChange, mode, readOnly = false }) => {
5 | const [editor, setEditor] = useState();
6 | const [cursor, setCursor] = useState({
7 | line: 0,
8 | ch: 0,
9 | });
10 |
11 | if (editor) {
12 | setEditor(undefined);
13 | }
14 |
15 | return (
16 |
17 | {
20 | setCursor({
21 | line: data.line,
22 | ch: data.ch + 1,
23 | });
24 | }}
25 | autoRefresh
26 | editorDidMount={(ed) => {
27 | setEditor(ed);
28 | }}
29 | onChange={(editor, data, value) => {
30 | if (onChange) onChange(value);
31 | }}
32 | value={value}
33 | height="100%"
34 | options={{
35 | mode,
36 | theme: 'material',
37 | lineNumbers: true,
38 | readOnly,
39 | }}
40 | />
41 |
42 | );
43 | };
44 |
45 | export default CodePart;
46 |
--------------------------------------------------------------------------------
/components/nav.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Sun, Moon, RotateCw } from 'react-feather';
3 |
4 | const Nav = ({ currentMode: { value }, changeMode }) => (
5 |
84 | );
85 |
86 | export default Nav;
87 |
--------------------------------------------------------------------------------
/helpers/index.js:
--------------------------------------------------------------------------------
1 | const ucwords = (string) => string.charAt(0).toUpperCase() + string.slice(1);
2 |
3 | const selectDatatype = (fieldLineRaw) => {
4 | const fieldLine = fieldLineRaw.toLowerCase();
5 |
6 | if (
7 | fieldLine.includes('varchar') ||
8 | fieldLine.includes('text') ||
9 | fieldLine.includes('char') ||
10 | fieldLine.includes('datetime')
11 | )
12 | return 'String';
13 |
14 | if (fieldLine.includes('decimal') || fieldLine.includes('float')) return 'Float';
15 |
16 | if (fieldLine.includes('tinyint') || fieldLine.includes('float')) return 'Boolean';
17 |
18 | if (
19 | fieldLine.includes('int') ||
20 | fieldLine.includes('bigint') ||
21 | fieldLine.includes('numeric') ||
22 | fieldLine.includes('real')
23 | )
24 | return 'Int';
25 | };
26 |
27 | const checkField = (lineRaw) => {
28 | const line = lineRaw.toLowerCase();
29 | const excludeKeywords = ['charset', 'constraint', 'foreign key', 'engine', 'create schema'];
30 |
31 | const isBlacklisted = excludeKeywords.some((keyword) => line.includes(keyword));
32 | if (isBlacklisted) {
33 | return false;
34 | }
35 |
36 | const dataTypes = [
37 | 'int',
38 | 'varchar',
39 | 'char',
40 | 'numeric',
41 | 'bigint',
42 | 'real',
43 | 'tinyint',
44 | 'decimal',
45 | 'text',
46 | 'float',
47 | 'datetime',
48 | ];
49 |
50 | const checkFields = dataTypes.map((it) => {
51 | if (line.includes(it)) return true;
52 | });
53 |
54 | if (checkFields.includes(true)) return true;
55 | return false;
56 | };
57 |
58 | const camelCase = (str) => {
59 | return (str.slice(0, 1).toLowerCase() + str.slice(1))
60 | .replace(/([-_ ]){1,}/g, ' ')
61 | .split(/[-_ ]/)
62 | .reduce((cur, acc) => {
63 | return cur + acc[0].toUpperCase() + acc.substring(1);
64 | });
65 | };
66 |
67 | const generateTableName = (str) => {
68 | return ucwords(camelCase(str));
69 | };
70 |
71 | const lineIsComment = (line) => {
72 | return line.startsWith('--') || line.startsWith("/*")
73 | }
74 |
75 | export { ucwords, selectDatatype, checkField, generateTableName, lineIsComment };
76 |
--------------------------------------------------------------------------------
/helpers/useLocalStorage.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 |
3 | const useLocalStorage = (key, initialValue) => {
4 | // State to store our value
5 | // Pass initial state function to useState so logic is only executed once
6 | const [storedValue, setStoredValue] = useState(() => {
7 | try {
8 | // Get from local storage by key
9 | const item = window.localStorage.getItem(key);
10 | // Parse stored json or if none return initialValue
11 | return item ? JSON.parse(item) : initialValue;
12 | } catch (error) {
13 | // If error also return initialValue
14 | console.log(error);
15 | return initialValue;
16 | }
17 | });
18 |
19 | // Return a wrapped version of useState's setter function that ...
20 | // ... persists the new value to localStorage.
21 | const setValue = (value) => {
22 | try {
23 | // Allow value to be a function so we have same API as useState
24 | const valueToStore = value instanceof Function ? value(storedValue) : value;
25 | // Save state
26 | setStoredValue(valueToStore);
27 | // Save to local storage
28 | window.localStorage.setItem(key, JSON.stringify(valueToStore));
29 | } catch (error) {
30 | // A more advanced implementation would handle the error case
31 | console.log(error);
32 | }
33 | };
34 |
35 | return [storedValue, setValue];
36 | };
37 |
38 | export default useLocalStorage;
39 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | const withCSS = require('@zeit/next-css');
2 | const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
3 |
4 | module.exports = withCSS({
5 | webpack(config, { dev }) {
6 | if (config.mode === 'production') {
7 | if (Array.isArray(config.optimization.minimizer)) {
8 | config.optimization.minimizer.push(new OptimizeCSSAssetsPlugin({}));
9 | }
10 | }
11 |
12 | return config;
13 | }
14 | });
15 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sql-graphql-schema-generator",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start"
9 | },
10 | "dependencies": {
11 | "@zeit/next-css": "^1.0.1",
12 | "codemirror": "^5.55.0",
13 | "codemirror-graphql": "^0.12.0",
14 | "graphql": "^15.3.0",
15 | "next": "9.3.2",
16 | "optimize-css-assets-webpack-plugin": "^5.0.3",
17 | "react": "^16.10.2",
18 | "react-codemirror2": "^7.2.1",
19 | "react-copy-to-clipboard": "^5.0.1",
20 | "react-dom": "16.10.2",
21 | "react-feather": "^2.0.3",
22 | "react-hotkeys-hook": "^1.5.3",
23 | "react-notify-toast": "^0.5.1",
24 | "sql-prettier": "^0.1.3",
25 | "use-dark-mode": "^2.3.1"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/pages/index.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import useDarkMode from 'use-dark-mode';
3 | import sqlPrettier from 'sql-prettier';
4 | import dynamic from 'next/dynamic';
5 |
6 | import { CopyToClipboard } from 'react-copy-to-clipboard';
7 | import { Play, Clipboard } from 'react-feather';
8 | import { useHotkeys } from 'react-hotkeys-hook';
9 | import Notifications, { notify } from 'react-notify-toast';
10 |
11 | import Head from 'next/head';
12 | // import useLocalStorage from '../helpers/useLocalStorage';
13 |
14 | import Nav from '../components/nav';
15 | import { generateTableName, selectDatatype, checkField, lineIsComment } from '../helpers/index';
16 | import './style.css';
17 |
18 | import 'codemirror/lib/codemirror.css';
19 | import('codemirror/mode/sql/sql.js');
20 | import('codemirror-graphql/mode');
21 | import('codemirror-graphql/hint');
22 | import('codemirror-graphql/lint');
23 | import('codemirror/addon/lint/lint');
24 | import('codemirror/addon/hint/show-hint');
25 |
26 | const CodePart = dynamic(() => import('../components/CodePart'), { ssr: false });
27 |
28 | const Home = () => {
29 | const [query, setQuery] = useState();
30 | const [schema, setSchema] = useState('');
31 | const darkMode = useDarkMode(true);
32 |
33 | useHotkeys('ctrl+v', async () => {
34 | const text = await navigator.clipboard.readText();
35 | setQuery(text);
36 | });
37 |
38 | useHotkeys('ctrl+space', () => {
39 | makeSchema();
40 | });
41 |
42 | const makeSchema = () => {
43 | const newQuery = sqlPrettier.format(query);
44 |
45 | const lines = newQuery.split(/[\r\n]+/).filter(r => r.trim().length > 0);
46 |
47 | let graphqlSchema = '';
48 |
49 | for (var i = 0; i < lines.length; i++) {
50 |
51 | const currentLine = lines[i].trim().replaceAll('`', '');
52 |
53 | if(lineIsComment(currentLine)) {
54 | continue;
55 | }
56 |
57 | if (currentLine.toLowerCase().includes('create table')) {
58 | const tableName = currentLine
59 | .toLowerCase()
60 | .replace('create table', '')
61 | .replace(/[^\w\s]/gi, '');
62 | graphqlSchema += `type ${generateTableName(tableName.trim())} {\r\n`;
63 | }
64 |
65 | if (currentLine.startsWith(')')) {
66 | graphqlSchema += `}\r\n\r\n`;
67 | }
68 |
69 | if (checkField(currentLine)) {
70 | const fieldLine = currentLine;
71 | const getField = fieldLine.substr(0, fieldLine.indexOf(' ')).replace(/[^\w\s]/gi, '');
72 | const notNull = fieldLine.includes('NOT NULL');
73 | let type = '';
74 | if (getField === 'id' || getField.includes('_id')) type = 'ID';
75 | else type = selectDatatype(fieldLine.toLowerCase());
76 | graphqlSchema += ` ${getField}: ${type}${notNull ? '!' : ''}\r\n`;
77 | }
78 |
79 | setSchema(graphqlSchema);
80 | }
81 | };
82 |
83 | return (
84 |
85 |
86 |
SQL to GraphQL Schema Generator
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
121 |
Run
122 |
123 |
124 | {schema && (
125 |
126 |
127 | notify.show('Copied!', 'success')}>
128 |
131 |
132 | Copy
133 |
134 |
135 | )}
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 | );
146 | };
147 |
148 | export default Home;
149 |
--------------------------------------------------------------------------------
/pages/style.css:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css?family=Montserrat&display=swap');
2 |
3 | body[class='light-mode'] {
4 | --background-color: #fff;
5 | --text-color: #000;
6 | --text-area-border: #eaeaea;
7 | --btn-color: #fff;
8 | --btn-hover: #565656;
9 | --btn-text-hover: #fbfbfb;
10 |
11 | --cm-variable: #555555;
12 | }
13 |
14 | body[class='dark-mode'] {
15 | --background-color: #0f111a;
16 | --text-color: #fff;
17 | --text-area-border: #1a1c25;
18 | --btn-color: #fff;
19 | --btn-hover: #1a1c24;
20 | --btn-text-hover: #4186f5;
21 | --cm-variable: #eeffff;
22 | }
23 |
24 | ::selection {
25 | background: #79ffe1;
26 | }
27 |
28 | ::-webkit-scrollbar-track {
29 | -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
30 | background-color: #191c25;
31 | }
32 |
33 | ::-webkit-scrollbar {
34 | width: 5px;
35 | background-color: #191c25;
36 | }
37 |
38 | ::-webkit-scrollbar-thumb {
39 | -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
40 | background-color: #555;
41 | }
42 |
43 | body {
44 | font-family: Montserrat;
45 | background: var(--background-color);
46 | color: var(--text-color);
47 | }
48 |
49 | button {
50 | border: 1px solid var(--text-area-border);
51 | background: var(--btn-hover);
52 | border-radius: 100%;
53 | padding: 1rem;
54 | cursor: pointer;
55 | margin-bottom: 20px;
56 | }
57 |
58 | button > svg {
59 | color: var(--btn-color);
60 | }
61 |
62 | button:hover {
63 | border: 1px solid var(--btn-text-hover) !important;
64 | }
65 |
66 | .logo {
67 | font-family: Montserrat;
68 | display: flex;
69 | align-items: center;
70 | }
71 | .logo svg {
72 | margin-right: 10px;
73 | }
74 |
75 | .tooltip {
76 | position: relative;
77 | background: #000;
78 | padding: 5px;
79 | color: white;
80 | border-radius: 2px;
81 | left: 15px;
82 | top: -13px;
83 | font-size: 12px;
84 | }
85 |
86 | .option .tooltip {
87 | display: none;
88 | }
89 | .option:hover {
90 | color: #f1f1f1;
91 | }
92 | .option:hover .tooltip {
93 | display: inline;
94 | }
95 |
96 | .pointer {
97 | cursor: pointer;
98 | }
99 |
100 | .play-icon {
101 | position: relative;
102 | left: 2px;
103 | color: #eaeaea;
104 | }
105 |
106 | button:hover {
107 | border: 1px solid var(--btn-hover);
108 | }
109 |
110 | button:hover > svg {
111 | color: var(--btn-text-hover);
112 | }
113 |
114 | button:focus {
115 | outline: 0;
116 | }
117 |
118 | textarea {
119 | width: 100%;
120 | resize: none;
121 | height: 400px;
122 | background-color: transparent;
123 | box-shadow: none;
124 | box-sizing: border-box;
125 | display: block;
126 | font-size: 14px;
127 | line-height: 1.7;
128 | border-radius: 0px;
129 | font-family: Montserrat;
130 | height: 100%;
131 | min-height: 100px;
132 | resize: none;
133 | width: 100%;
134 | color: var(--text-color);
135 | border-radius: 0px;
136 | border-width: initial;
137 | border-style: none;
138 | border-color: initial;
139 | border-image: initial;
140 | outline: 0px;
141 | border: 1px solid var(--text-area-border);
142 | padding: 1rem;
143 | }
144 |
145 | .flex {
146 | display: flex;
147 | }
148 | .h-90 {
149 | height: 90vh;
150 | }
151 | .flex div {
152 | flex: 1;
153 | }
154 | .btn-wrapper {
155 | display: flex;
156 | justify-content: center;
157 | align-items: center;
158 | width: 0;
159 | z-index: 999;
160 | display: flex;
161 | flex-direction: column;
162 | }
163 |
164 | .hero {
165 | width: 100%;
166 | color: #333;
167 | }
168 | .title {
169 | margin: 0;
170 | width: 100%;
171 | padding-top: 80px;
172 | line-height: 1.15;
173 | font-size: 48px;
174 | }
175 | .title,
176 | .description {
177 | text-align: center;
178 | }
179 | .row {
180 | max-width: 880px;
181 | margin: 80px auto 40px;
182 | display: flex;
183 | flex-direction: row;
184 | justify-content: space-around;
185 | }
186 |
187 | .top-border {
188 | border: 1px solid var(--text-area-border);
189 | }
190 | .right-border {
191 | border-right: 1px solid var(--text-area-border);
192 | }
193 | .border-left {
194 | border-left: 1px solid var(--text-area-border);
195 | }
196 |
197 | .spons {
198 | display: flex;
199 | align-items: center;
200 | }
201 |
202 | .spons .msg {
203 | margin-right: 10px;
204 | font-weight: 600;
205 | }
206 | .spons span {
207 | color: var(--text-color);
208 | }
209 | .spons img {
210 | margin-right: 10px;
211 | }
212 |
213 | .pl-2 {
214 | padding-left: 2rem !important;
215 | padding-right: 2rem !important;
216 | background: var(--text-area-border);
217 | border-left: 1px solid #128cf0;
218 | }
219 |
220 | .theme-switch {
221 | padding: 0 2rem !important;
222 | }
223 |
224 | /* Codemirror */
225 | .CodeMirror {
226 | font-family: Montserrat !important;
227 | min-height: 90vh !important;
228 | max-width: calc(100vw - 50vw);
229 | line-height: 1.2em;
230 | }
231 | .cm-s-material.CodeMirror {
232 | background-color: var(--background);
233 | color: var(--cm-variable);
234 | }
235 |
236 | .cm-s-material .CodeMirror-gutters {
237 | background: var(--background);
238 | color: #546e7a;
239 | border: none;
240 | }
241 |
242 | .cm-s-material .CodeMirror-guttermarker,
243 | .cm-s-material .CodeMirror-guttermarker-subtle,
244 | .cm-s-material .CodeMirror-linenumber {
245 | color: #546e7a;
246 | }
247 | .cm-s-material .CodeMirror-lines {
248 | line-height: 24px;
249 | }
250 |
251 | .cm-s-material .CodeMirror-cursor {
252 | border-left: 1px solid #ffcc00;
253 | }
254 |
255 | .cm-s-material div.CodeMirror-selected {
256 | background: rgba(128, 203, 196, 0.2);
257 | }
258 |
259 | .cm-s-material.CodeMirror-focused div.CodeMirror-selected {
260 | background: rgba(128, 203, 196, 0.2);
261 | }
262 |
263 | .cm-s-material .CodeMirror-line::selection,
264 | .cm-s-material .CodeMirror-line > span::selection,
265 | .cm-s-material .CodeMirror-line > span > span::selection {
266 | background: rgba(128, 203, 196, 0.2);
267 | }
268 |
269 | .cm-s-material .CodeMirror-line::-moz-selection,
270 | .cm-s-material .CodeMirror-line > span::-moz-selection,
271 | .cm-s-material .CodeMirror-line > span > span::-moz-selection {
272 | background: rgba(128, 203, 196, 0.2);
273 | }
274 |
275 | .cm-s-material .CodeMirror-activeline-background {
276 | background: rgba(0, 0, 0, 0.5);
277 | }
278 |
279 | .cm-s-material .cm-keyword {
280 | color: #c792ea;
281 | }
282 |
283 | .cm-s-material .cm-operator {
284 | color: #89ddff;
285 | }
286 |
287 | .cm-s-material .cm-variable-2 {
288 | color: var(--cm-variable);
289 | }
290 |
291 | .cm-s-material .cm-variable-3,
292 | .cm-s-material .cm-type {
293 | color: #f07178;
294 | }
295 |
296 | .cm-s-material .cm-builtin {
297 | color: #ffcb6b;
298 | }
299 |
300 | .cm-s-material .cm-atom {
301 | color: #f78c6c;
302 | }
303 |
304 | .cm-s-material .cm-number {
305 | color: #ff5370;
306 | }
307 |
308 | .cm-s-material .cm-def {
309 | color: #82aaff;
310 | }
311 |
312 | .cm-s-material .cm-string {
313 | color: #c3e88d;
314 | }
315 |
316 | .cm-s-material .cm-string-2 {
317 | color: #f07178;
318 | }
319 |
320 | .cm-s-material .cm-comment {
321 | color: #546e7a;
322 | }
323 |
324 | .cm-s-material .cm-variable {
325 | color: #f07178;
326 | }
327 |
328 | .cm-s-material .cm-tag {
329 | color: #ff5370;
330 | }
331 |
332 | .cm-s-material .cm-meta {
333 | color: #ffcb6b;
334 | }
335 |
336 | .cm-s-material .cm-attribute {
337 | color: #c792ea;
338 | }
339 |
340 | .cm-s-material .cm-property {
341 | color: #c792ea;
342 | }
343 |
344 | .cm-s-material .cm-qualifier {
345 | color: #decb6b;
346 | }
347 |
348 | .cm-s-material .cm-variable-3,
349 | .cm-s-material .cm-type {
350 | color: #decb6b;
351 | }
352 |
353 | .cm-s-material .cm-error {
354 | color: rgba(255, 255, 255, 1);
355 | background-color: #ff5370;
356 | }
357 |
358 | .cm-s-material .CodeMirror-matchingbracket {
359 | text-decoration: underline;
360 | color: white !important;
361 | }
362 |
--------------------------------------------------------------------------------
/preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Devzstudio/SQL-to-Graphql-Schema-Generator/fd875da4f1029c3b2a37b9242233b2afff9bd34f/preview.png
--------------------------------------------------------------------------------
/public/cover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Devzstudio/SQL-to-Graphql-Schema-Generator/fd875da4f1029c3b2a37b9242233b2afff9bd34f/public/cover.png
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Devzstudio/SQL-to-Graphql-Schema-Generator/fd875da4f1029c3b2a37b9242233b2afff9bd34f/public/favicon.ico
--------------------------------------------------------------------------------