diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..42e5a50
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,11 @@
+.DS_Store
+node_modules
+/build
+/.svelte-kit
+/package
+.env
+.env.*
+!.env.example
+vite.config.js.timestamp-*
+vite.config.ts.timestamp-*
+/db
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..e69de29
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..1a38a32
--- /dev/null
+++ b/package.json
@@ -0,0 +1,23 @@
+{
+ "name": "sanifae",
+ "version": "0.0.1",
+ "private": true,
+ "scripts": {
+ "dev": "vite dev",
+ "build": "vite build",
+ "preview": "vite preview"
+ },
+ "devDependencies": {
+ "@sveltejs/adapter-auto": "^1.0.0",
+ "@sveltejs/kit": "^1.0.0",
+ "svelte": "^3.54.0",
+ "vite": "^4.0.0"
+ },
+ "type": "module",
+ "dependencies": {
+ "@sveltejs/adapter-node": "^1.1.4",
+ "bcrypt": "^5.1.0",
+ "sqlite": "^4.1.2",
+ "sqlite3": "^5.1.4"
+ }
+}
diff --git a/src/app.html b/src/app.html
new file mode 100644
index 0000000..4c57e56
--- /dev/null
+++ b/src/app.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+ %sveltekit.head%
+
+
+ %sveltekit.body%
+
+
diff --git a/src/lib/components/Area.svelte b/src/lib/components/Area.svelte
new file mode 100644
index 0000000..bc0c3f0
--- /dev/null
+++ b/src/lib/components/Area.svelte
@@ -0,0 +1,71 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/lib/components/Button.svelte b/src/lib/components/Button.svelte
new file mode 100644
index 0000000..8e2f6c2
--- /dev/null
+++ b/src/lib/components/Button.svelte
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/lib/components/FileUpload.svelte b/src/lib/components/FileUpload.svelte
new file mode 100644
index 0000000..65cc312
--- /dev/null
+++ b/src/lib/components/FileUpload.svelte
@@ -0,0 +1,80 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/lib/components/Header.svelte b/src/lib/components/Header.svelte
new file mode 100644
index 0000000..5819f37
--- /dev/null
+++ b/src/lib/components/Header.svelte
@@ -0,0 +1,58 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/lib/db/db.js b/src/lib/db/db.js
new file mode 100644
index 0000000..61b7af1
--- /dev/null
+++ b/src/lib/db/db.js
@@ -0,0 +1,95 @@
+const FILE_SIZE_LIMIT = 1024*1024*16;
+
+const VALID_EXTENSIONS = ['png','jpg','jpeg','svg'];
+
+var ridArray = {};
+
+import sqlite3 from 'sqlite3'
+import { writeFile } from 'node:fs/promises';
+import { safePath, user } from '../util.js';
+import { open } from 'sqlite'
+import { createHash } from 'node:crypto';
+
+var db;
+async function initDb() {
+ db = await open({
+ filename: `${process.cwd()}/db/main.db`,
+ driver: sqlite3.Database
+ });
+
+ await db.run('CREATE TABLE IF NOT EXISTS rating (id CHAR(256), rating INTEGER)');
+}
+
+let backendProxy = async ({route, backendParams,}) => {
+ if (!db) await initDb();
+
+ const res = await fetch(`https://sanifae.insfa.net/api/token`,{
+ 'method': 'GET', 'headers': {
+ 'cookie': backendParams.cookie
+ }
+ });
+
+ backendParams['admin'] = ((await res.json()).data == user);
+
+ return backend[route](backendParams);
+}
+
+var backend = {};
+
+
+backend.rateOc = async ({admin,id,rating}) => {
+ if (admin) {
+ await db.run('UPDATE rating SET rating = ? WHERE id = ?', [
+ rating,
+ id
+ ])
+ }
+};
+
+backend.oc = async ({admin}) => {
+ return await db.all('SELECT * FROM rating WHERE rating > ? ORDER BY rating DESC', [
+ (admin) ? -1000 : -1
+ ]);
+}
+
+backend.fileCreate = async({img, extension,id, last }) => {
+ if (ridArray[id] !== '' && !(ridArray[id])) {
+ ridArray[id] = img;
+ } else {
+ ridArray[id] += img;
+ }
+
+ const imgData = ridArray[id];
+
+ if (last != 'true') {
+ return {'success': 'Image still proccessing...'}
+ } else {
+ ridArray[id] = false;
+ }
+
+ const imgHash = createHash('md5').update(imgData).digest('hex');
+
+ if (!imgHash)
+ return {'success': 'Image not provided.'}
+
+ if (imgHash.length > FILE_SIZE_LIMIT)
+ return {'success': 'Image too big.'}
+
+ const extensionSafe = safePath(extension);
+
+ if (VALID_EXTENSIONS.indexOf(extensionSafe) == -1)
+ return { success: 'Illegal file extension. Permitted file extensions are: ' + VALID_EXTENSIONS.join(', ') };
+
+ writeFile(`${process.cwd()}/db/post-${imgHash}.${extensionSafe}`,imgData,{encoding: 'base64'});
+
+ await db.run('INSERT INTO rating (id,rating) VALUES (?,?)', [
+ `${imgHash}.${extensionSafe}`,
+ -1
+ ]);
+
+ return { success: 'Successfully uploaded file.', 'href': `/img/${imgHash}.${extensionSafe}`};
+}
+export {
+ backendProxy,
+ backend
+}
\ No newline at end of file
diff --git a/src/lib/util.js b/src/lib/util.js
new file mode 100644
index 0000000..3503b1e
--- /dev/null
+++ b/src/lib/util.js
@@ -0,0 +1,85 @@
+const EXTENSION_MAP = {
+ 'png': 'img',
+ 'jpg': 'img',
+ 'jpeg': 'img',
+ 'gif': 'img',
+ 'svg': 'img',
+ 'mp4': 'video'
+}
+
+let checkLength = function(string, field, lowerBound, upperBound) {
+ if (string.length < lowerBound) {
+ if (string.length == 0) {
+ return { success: `${field} cannot be blank.` }
+ }
+ return { success: `${field} is too short (minimum length: ${lowerBound} characters).` }
+ }
+
+ if (string.length > upperBound) {
+ return { success: `${field} is too long (maximum length: ${upperBound} characters).` }
+ }
+
+ return false;
+}
+
+let checkRegex = function(string, field, regex) {
+ if (string.search(regex) != -1) {
+ return { success: `${field} contains illegal characters.` }
+ }
+ return false;
+}
+
+let handleSubmitGet = async e => {
+ const ACTION_URL = e.target.action
+
+ const formData = new FormData(e.target);
+ const asString = new URLSearchParams(formData).toString();
+
+ return await fetch(ACTION_URL + '?' + asString, {
+ method: 'GET',
+ }).then(x => x.text());
+}
+
+let handleSubmit = async e => {
+ const ACTION_URL = e.target.action
+
+ const formData = new FormData(e.target);
+
+ return await fetch(ACTION_URL, {
+ method: 'POST',
+ body: formData
+ }).then(x => x.text());
+}
+
+let safeName = function (text) {
+ return text.replaceAll(/[^A-Za-z0-9\-\_]/g, '');
+}
+
+let block = function(bool) {
+ return (bool) ? 'block' : 'inline';
+}
+
+let safePath = function(path) {
+ if (path == '..' || path == '.') return '';
+ return path.replace(/[\/]+/g, '')
+}
+
+let setLocation = function(location, key, value) {
+ var loc = new URL(location).searchParams;
+
+ loc.set(key,value);
+ return loc.toString();
+}
+
+let user = 'derv';
+
+export {
+ checkLength,
+ checkRegex,
+ handleSubmit,
+ handleSubmitGet,
+ block,
+ safePath,
+ setLocation,
+ user
+};
\ No newline at end of file
diff --git a/src/routes/+layout.js b/src/routes/+layout.js
new file mode 100644
index 0000000..a0c8ba5
--- /dev/null
+++ b/src/routes/+layout.js
@@ -0,0 +1,8 @@
+/** @type {import('./$types').PageLoad} */
+export async function load({ fetch }) {
+ const res = await fetch(`/api/token`);
+
+ const username = await res.json();
+
+ return { username: username.data };
+}
\ No newline at end of file
diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte
new file mode 100644
index 0000000..c431840
--- /dev/null
+++ b/src/routes/+layout.svelte
@@ -0,0 +1,46 @@
+
+
+
+
+
+
diff --git a/src/routes/+page.server.js b/src/routes/+page.server.js
new file mode 100644
index 0000000..7d482a4
--- /dev/null
+++ b/src/routes/+page.server.js
@@ -0,0 +1,19 @@
+import { user } from '$lib/util.js';
+
+/** @type {import('./$types').PageServerLoad} */
+export async function load({ fetch, params, url, cookies, request }) {
+
+ const res = await fetch(`https://sanifae.insfa.net/api/token`,{
+ 'method': 'GET', 'headers': {
+ 'cookie': request.headers.get('cookie')
+ }
+ });
+
+ const userJson = await res.json();
+
+ const oc = await fetch(`/api/oc?user=${encodeURIComponent(user)}`,{
+ 'method': 'GET'
+ }).then(x => x.json());
+
+ return { data: userJson.data == user, oc, token: request.headers.get('cookie') };
+}
\ No newline at end of file
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte
new file mode 100644
index 0000000..97562e5
--- /dev/null
+++ b/src/routes/+page.svelte
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+ OC Rating Stand
+
+
+
Put your OC and wait for it to be rated! SVGs (vector), PNGs (bitmap), and other image formats can be uploaded.
+
+
+
+
+
+
+
+ Rated OCs
+
+
+ {#each data.oc as ocData}
+
+
+
+
+ {ocData.rating == '-1' ? 'Unrated' : ocData.rating}
+ / 10
+
+
await fetch(`/api/rateOc?rating=${encodeURIComponent(editable[ocData.id].textContent)}&id=${encodeURIComponent(ocData.id)}`)}>
+ Update
+
+
+ {/each}
+
+
+
+
+
\ No newline at end of file
diff --git a/src/routes/api/[route]/+server.js b/src/routes/api/[route]/+server.js
new file mode 100644
index 0000000..563587f
--- /dev/null
+++ b/src/routes/api/[route]/+server.js
@@ -0,0 +1,51 @@
+import { backend, backendProxy } from '../../../lib/db/db.js';
+
+
+/** @type {import('./$types').RequestHandler} */
+export async function GET({ url, cookies, params, fetch, request }) {
+ var formEntries = url.searchParams;
+
+ formEntries = [...formEntries,['cookie',request.headers.get('cookie')]];
+
+ return await handleReq({
+ cookies,
+ params: formEntries,
+ route: params.route,
+ fetch
+ });
+}
+
+/** @type {import('./$types').RequestHandler} */
+export async function POST({ cookies, request, params, fetch }) {
+
+ var formEntries = (await request.formData()).entries();
+
+ formEntries = [...formEntries,['cookie',request.headers.get('cookie')]];
+
+ return await handleReq({
+ cookies,
+ params: formEntries,
+ route: params.route,
+ fetch
+ });
+}
+
+async function handleReq({ cookies, params, route, fetch }) {
+ var backendParams = {cookies,fetch};
+
+ for (const [key, value] of params) {
+ backendParams[key] = value + '';
+ }
+
+ return await mainApi({backendParams, route: route});
+}
+
+async function mainApi({backendParams, route, fetch}) {
+ if (Object.keys(backend).indexOf(route) == -1) {
+ return new Response(JSON.stringify({success: 'route doesn\'t exist'}));
+ }
+
+ var resData = await backendProxy({ route, backendParams });
+
+ return new Response(JSON.stringify(resData));
+};
\ No newline at end of file
diff --git a/src/routes/img/[img]/+server.js b/src/routes/img/[img]/+server.js
new file mode 100644
index 0000000..eaf5df7
--- /dev/null
+++ b/src/routes/img/[img]/+server.js
@@ -0,0 +1,20 @@
+import { readFile } from 'node:fs/promises';
+
+/** @type {import('./$types').RequestHandler} */
+export async function GET({ url, cookies, params }) {
+ var imgName = params['img'];
+
+ imgName = imgName.replace(/(\s+)/g, '\\$1');
+
+ var res = await readFile(`${process.cwd()}/db/post-${imgName}`);
+
+ var response = new Response(res);
+ var extension = imgName.split('.').pop();
+
+ if (extension == 'svg') {
+ response = new Response(res, {'headers': {
+ 'Content-Type': 'image/svg+xml'
+ }});
+ }
+ return response;
+}
diff --git a/src/routes/login/+page.svelte b/src/routes/login/+page.svelte
new file mode 100644
index 0000000..8e34067
--- /dev/null
+++ b/src/routes/login/+page.svelte
@@ -0,0 +1,43 @@
+
+
+
+
+ Log in
+
+
+
+
+
+
+
+ {#if form?.success}
+
{form?.success}
+ {/if}
+ By using the Sanifae service, you agree to the Terms of Service .
+
+
\ No newline at end of file
diff --git a/static/YourOC.svg b/static/YourOC.svg
new file mode 100644
index 0000000..62323a6
--- /dev/null
+++ b/static/YourOC.svg
@@ -0,0 +1 @@
+Your OC
\ No newline at end of file
diff --git a/static/delete.svg b/static/delete.svg
new file mode 100644
index 0000000..218d0fe
--- /dev/null
+++ b/static/delete.svg
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/static/downvote.svg b/static/downvote.svg
new file mode 100644
index 0000000..4ef8081
--- /dev/null
+++ b/static/downvote.svg
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/static/favicon.png b/static/favicon.png
new file mode 100644
index 0000000..825b9e6
Binary files /dev/null and b/static/favicon.png differ
diff --git a/static/icon_sanifae.svg b/static/icon_sanifae.svg
new file mode 100644
index 0000000..44ef5b8
--- /dev/null
+++ b/static/icon_sanifae.svg
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/static/icon_sanifae_black.svg b/static/icon_sanifae_black.svg
new file mode 100644
index 0000000..b8f8bbe
--- /dev/null
+++ b/static/icon_sanifae_black.svg
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/static/logo_sanifae.svg b/static/logo_sanifae.svg
new file mode 100644
index 0000000..54e106f
--- /dev/null
+++ b/static/logo_sanifae.svg
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/static/upvote.svg b/static/upvote.svg
new file mode 100644
index 0000000..8e113b7
--- /dev/null
+++ b/static/upvote.svg
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/static/view.svg b/static/view.svg
new file mode 100644
index 0000000..c181cc3
--- /dev/null
+++ b/static/view.svg
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/svelte.config.js b/svelte.config.js
new file mode 100644
index 0000000..6bfb3c4
--- /dev/null
+++ b/svelte.config.js
@@ -0,0 +1,10 @@
+import adapter from '@sveltejs/adapter-node';
+
+/** @type {import('@sveltejs/kit').Config} */
+const config = {
+ kit: {
+ adapter: adapter()
+ }
+};
+
+export default config;
diff --git a/vite.config.js b/vite.config.js
new file mode 100644
index 0000000..11f6c22
--- /dev/null
+++ b/vite.config.js
@@ -0,0 +1,7 @@
+import { sveltekit } from '@sveltejs/kit/vite';
+
+const config = {
+ plugins: [sveltekit()]
+};
+
+export default config;