-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
75c2bb6
commit 207ab63
Showing
3 changed files
with
207 additions
and
118 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,35 +1,187 @@ | ||
import { createClient } from 'https://cdn.jsdelivr.net/npm/@supabase/supabase-js/+esm' | ||
export const SUPABASE_URL = "https://fohgyexhzptaxjqrrrfd.supabase.co"; | ||
export const SUPABASE_ANON_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImZvaGd5ZXhoenB0YXhqcXJycmZkIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MDUwMjQxNTcsImV4cCI6MjAyMDYwMDE1N30.fa7XvwiBbWSe2MLIR6Wkh_OC95uV7UXxt7_25PlyAlc" | ||
export const SUPABASE = createClient(SUPABASE_URL, SUPABASE_ANON_KEY); | ||
export default SUPABASE = createClient(SUPABASE_URL, SUPABASE_ANON_KEY); | ||
window.SUPABASE = SUPABASE; | ||
|
||
// #region Type definitions | ||
/** | ||
* @typedef {Object} User | ||
* @property {string} id | ||
* @property {string} join_date | ||
* @property {string} username | ||
* @property {AccountState} account_state | ||
* @property {Role} role | ||
* @property {string} bio | ||
*/ | ||
|
||
/** | ||
* @typedef {Object} Submission | ||
* @property {string} id | ||
* @property {string} game_mode | ||
* @property {string} username | ||
* @property {Object} score | ||
* @property {Object | null} score | ||
* @property {string} upload_date | ||
* @property {string} replay_date | ||
* @property {string} replay_path | ||
* @property {string?} replay_date | ||
* @property {string} submitted_by | ||
* @property {SubmissionValidity} validity | ||
* @property {string?} proof | ||
*/ | ||
|
||
/** | ||
* @typedef {Object} ReplayData | ||
* @property {string} submission_id | ||
* @property {string} replay_data - Base64 encoded replay data | ||
*/ | ||
|
||
/** | ||
* @readonly | ||
* @enum {string} | ||
*/ | ||
export const AccountState = { | ||
NORMAL: "normal", | ||
BANNED: "banned", | ||
UNVERIFIED: "unverified" | ||
} | ||
NORMAL: "Normal", | ||
BANNED: "Banned", | ||
UNVERIFIED: "Unverified" | ||
} | ||
|
||
/** | ||
* @readonly | ||
* @enum {string} | ||
*/ | ||
export const Role = { | ||
USER: "User", | ||
VERIFIER: "Verifier", | ||
ADMIN: "Administrator" | ||
} | ||
|
||
/** | ||
* @readonly | ||
* @enum {string} | ||
*/ | ||
export const SubmissionValidity = { | ||
UNVERIFIED: "Unverified", | ||
VERIFIED: "Verified", | ||
SUSPICIOUS: "Suspicious", | ||
REJECTED: "Rejected" | ||
} | ||
// #endregion | ||
|
||
export const TABLES = { | ||
PROFILES: "profiles", | ||
SUBMISSIONS: "submissions", | ||
REPLAYS: "replays", | ||
} | ||
|
||
// #region Profile table functions | ||
export async function getAllProfiles(limit = 10, offset = 0) { | ||
const { data, error } = await SUPABASE.from(TABLES.PROFILES) | ||
.select('*') | ||
.range(offset, limit); | ||
|
||
if(error) throw new Error(error.message); | ||
return data; | ||
} | ||
export async function getProfileById(id) { | ||
const { data, error } = await SUPABASE.from(TABLES.PROFILES) | ||
.select(1) | ||
.eq('id', id); | ||
|
||
if(error) throw new Error(error.message); | ||
return data[0]; | ||
} | ||
export async function getProfileByUsername(username) { | ||
const { data, error } = await SUPABASE.from(TABLES.PROFILES) | ||
.select(1) | ||
.eq('username', username); | ||
|
||
if(error) throw new Error(error.message); | ||
return data[0]; | ||
} | ||
|
||
export async function editProfile(id, newProfileData) { | ||
const { data, error } = await SUPABASE.from(TABLES.PROFILES) | ||
.update(newProfileData) | ||
.eq('id', id); | ||
|
||
if(error) throw new Error(error.message); | ||
return data; | ||
} | ||
// #endregion | ||
|
||
// #region Submission table functions | ||
export async function getSubmissionsByGameMode(gameMode, limit = 10, offset = 0) { | ||
const { data, error } = await SUPABASE.from(TABLES.SUBMISSIONS) | ||
.select('*') | ||
.eq('game_mode', gameMode) | ||
.range(offset, limit); | ||
|
||
if(error) throw new Error(error.message); | ||
return data; | ||
} | ||
export async function getSubmissionById(id) { | ||
const { data, error } = await SUPABASE.from(TABLES.SUBMISSIONS) | ||
.select(1) | ||
.eq('id', id); | ||
|
||
if(error) throw new Error(error.message); | ||
return data[0]; | ||
} | ||
export async function getSubmissionByUserId(userId, limit = 10, offset = 0) { | ||
const { data, error } = await SUPABASE.from(TABLES.SUBMISSIONS) | ||
.select('*') | ||
.eq('submitted_by', userId) | ||
.range(offset, limit); | ||
|
||
if(error) throw new Error(error.message); | ||
return data; | ||
} | ||
|
||
export async function createSubmission(submissionData) { | ||
const { data, error } = await SUPABASE.from(TABLES.SUBMISSIONS) | ||
.insert(submissionData); | ||
|
||
if(error) throw new Error(error.message); | ||
return data; | ||
} | ||
export async function editSubmission(id, newSubmissionData) { | ||
const { data, error } = await SUPABASE.from(TABLES.SUBMISSIONS) | ||
.update(newSubmissionData) | ||
.eq('id', id); | ||
|
||
if(error) throw new Error(error.message); | ||
return data; | ||
} | ||
export async function deleteSubmission(id) { | ||
const { data, error } = await SUPABASE.from(TABLES.SUBMISSIONS) | ||
.delete() | ||
.eq('id', id); | ||
|
||
if(error) throw new Error(error.message); | ||
return data; | ||
} | ||
// #endregion | ||
|
||
// #region Replay table functions | ||
export async function getReplayDataBySubmissionId(submissionId) { | ||
const { data, error } = await SUPABASE.from(TABLES.REPLAYS) | ||
.select('*') | ||
.eq('submission_id', submissionId); | ||
|
||
if(error) throw new Error(error.message); | ||
return data[0]; | ||
} | ||
|
||
export async function createReplayData(replayData) { | ||
const { data, error } = await SUPABASE.from(TABLES.REPLAYS) | ||
.insert(replayData); | ||
|
||
if(error) throw new Error(error.message); | ||
return data; | ||
} | ||
export async function deleteReplayData(submissionId) { | ||
const { data, error } = await SUPABASE.from(TABLES.REPLAYS) | ||
.delete() | ||
.eq('submission_id', submissionId); | ||
|
||
if(error) throw new Error(error.message); | ||
return data; | ||
} | ||
// #endregion |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,117 +1,54 @@ | ||
import { SUPABASE } from "./db.js"; | ||
|
||
let NSFWJSInstance = null; | ||
|
||
{ // Counter for character count | ||
for(const counter of document.getElementsByClassName('char-count')) { | ||
const associatedId = counter.getAttribute('for'); | ||
if(!associatedId) { | ||
console.error('No associated input found for: ', counter); | ||
continue; | ||
} | ||
|
||
const associatedInput = document.getElementById(associatedId); | ||
if(!associatedInput) { | ||
console.error('No input found with id: ', associatedId); | ||
continue; | ||
} | ||
|
||
associatedInput.addEventListener('input', () => updateCounter(counter)); | ||
updateCounter(counter); | ||
{ | ||
const ELEMENTS = { | ||
form: document.getElementById('profile-setup'), | ||
profileImageInput: document.getElementById('pfp-input'), | ||
profileImagePreview: document.getElementById('pfp-preview'), | ||
usernameInput: document.getElementById('username'), | ||
bioInput: document.getElementById('bio'), | ||
charCounters: document.getElementsByClassName('char-count'), | ||
} | ||
|
||
function updateCounter(element) { | ||
const associatedId = element.getAttribute('for'); | ||
if(!associatedId) { | ||
console.error('No associated input found for: ', element); | ||
return; | ||
} | ||
|
||
const associatedInput = document.getElementById(associatedId); | ||
if(!associatedInput) { | ||
console.error('No input found with id: ', associatedId); | ||
return; | ||
} | ||
// #region Handle profile image preview | ||
{ | ||
ELEMENTS.profileImageInput?.addEventListener('change', () => { | ||
const file = profileImageInput.files[0]; | ||
if(!file) return; | ||
|
||
const maxChars = Number(associatedInput.getAttribute('maxlength') ?? Infinity); | ||
const currentChars = associatedInput.value.length; | ||
element.textContent = `${currentChars} / ${maxChars}`; | ||
const reader = new FileReader(); | ||
reader.onload = () => { | ||
ELEMENTS.profileImagePreview.src = reader.result; | ||
}; | ||
reader.readAsDataURL(file); | ||
}); | ||
} | ||
} | ||
|
||
{ // Handle profile image upload | ||
const profileImageInput = document.getElementById('pfp-input'); | ||
profileImageInput?.addEventListener('change', () => { | ||
const file = profileImageInput.files[0]; | ||
if(!file) return; | ||
|
||
const reader = new FileReader(); | ||
reader.onload = () => { | ||
const image = document.getElementById('pfp-preview'); | ||
if(image) { | ||
image.src = reader.result; | ||
// #endregion | ||
|
||
// #region Char counter | ||
{ | ||
for(const counter of ELEMENTS.charCounters) { | ||
const associatedId = counter.getAttribute('for'); | ||
if(!associatedId) { | ||
console.error('No associated input found for: ', counter); | ||
continue; | ||
} | ||
}; | ||
reader.readAsDataURL(file); | ||
}); | ||
} | ||
|
||
{ // Handle NSFWJS | ||
const MODEL_TYPE = "MobileNetV2"; | ||
const INDEXEDDB_MODEL_KEY = "nsfwjs-model"; | ||
async function loadNSFWJS() { | ||
if(!("indexedDB" in window)) { | ||
NSFWJSInstance = await nsfwjs.load(MODEL_TYPE); | ||
return; | ||
} | ||
|
||
if(await isModelCached()) { | ||
const cachedInstance = await nsfwjs.load(`indexeddb://${INDEXEDDB_MODEL_KEY}`); | ||
if(cachedInstance) { | ||
NSFWJSInstance = cachedInstance; | ||
return; | ||
|
||
const associatedInput = document.getElementById(associatedId); | ||
if(!associatedInput) { | ||
console.error('No input found with id: ', associatedId); | ||
continue; | ||
} | ||
} | ||
|
||
const instance = await nsfwjs.load(MODEL_TYPE); | ||
await instance.model.save(`indexeddb://${INDEXEDDB_MODEL_KEY}`); | ||
|
||
NSFWJSInstance = instance; | ||
} | ||
|
||
async function isModelCached() { | ||
return new Promise((resolve, reject) => { | ||
const openRequest = indexedDB.open("tensorflowjs"); | ||
|
||
openRequest.onupgradeneeded = () => { | ||
// The database did not previously exist, so it means the model is not saved | ||
resolve(false); | ||
}; | ||
|
||
openRequest.onsuccess = () => { | ||
const db = openRequest.result; | ||
const transaction = db.transaction("model_info_store", "readonly"); // tensorflow.js uses this object store name | ||
const store = transaction.objectStore("model_info_store"); | ||
const getRequest = store.get(INDEXEDDB_MODEL_KEY); | ||
|
||
getRequest.onsuccess = () => { | ||
if (getRequest.result) { | ||
resolve(true); | ||
} else { | ||
resolve(false); | ||
} | ||
}; | ||
|
||
getRequest.onerror = () => { | ||
reject(getRequest.error); | ||
}; | ||
}; | ||
|
||
openRequest.onerror = () => { | ||
reject(openRequest.error); | ||
}; | ||
}); | ||
associatedInput.addEventListener('input', function() { | ||
const maxChars = Number(associatedInput.getAttribute('maxlength') ?? Infinity); | ||
const currentChars = associatedInput.value.length; | ||
counter.textContent = `${currentChars} / ${maxChars}`; | ||
}); | ||
updateCounter(counter); | ||
} | ||
} | ||
|
||
loadNSFWJS(); | ||
} | ||
// #endregion | ||
} | ||
NSFWJSInstance = await nsfwjs.load("MobileNetV2"); |