From 3fca233c3e6417853e89142f743060ca98f6f972 Mon Sep 17 00:00:00 2001 From: Marcel Lorbeer Date: Wed, 14 Jun 2023 14:34:30 +0200 Subject: [PATCH] First commit --- .gitignore | 6 + aftman.toml | 7 + default.project.json | 12 ++ src/MarcSync/MarcSyncv0.5.lua | 211 +++++++++++++++++++++ src/MarcSync/Objects/Collection.lua | 109 +++++++++++ src/MarcSync/Objects/Entry.lua | 62 ++++++ src/MarcSync/Types/MARCSYNC_COLLECTION.lua | 1 + src/MarcSync/Types/MARCSYNC_ENTRY.lua | 1 + src/MarcSync/Types/MARCSYNC_UTILS.lua | 1 + src/MarcSync/Utils.lua | 64 +++++++ 10 files changed, 474 insertions(+) create mode 100644 .gitignore create mode 100644 aftman.toml create mode 100644 default.project.json create mode 100644 src/MarcSync/MarcSyncv0.5.lua create mode 100644 src/MarcSync/Objects/Collection.lua create mode 100644 src/MarcSync/Objects/Entry.lua create mode 100644 src/MarcSync/Types/MARCSYNC_COLLECTION.lua create mode 100644 src/MarcSync/Types/MARCSYNC_ENTRY.lua create mode 100644 src/MarcSync/Types/MARCSYNC_UTILS.lua create mode 100644 src/MarcSync/Utils.lua diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0694838 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# Project place file +/client.rbxlx + +# Roblox Studio lock files +/*.rbxlx.lock +/*.rbxl.lock \ No newline at end of file diff --git a/aftman.toml b/aftman.toml new file mode 100644 index 0000000..9b8f812 --- /dev/null +++ b/aftman.toml @@ -0,0 +1,7 @@ +# This file lists tools managed by Aftman, a cross-platform toolchain manager. +# For more information, see https://github.com/LPGhatguy/aftman + +# To add a new tool, add an entry to this table. +[tools] +rojo = "rojo-rbx/rojo@7.3.0" +# rojo = "rojo-rbx/rojo@6.2.0" \ No newline at end of file diff --git a/default.project.json b/default.project.json new file mode 100644 index 0000000..5b11d56 --- /dev/null +++ b/default.project.json @@ -0,0 +1,12 @@ +{ + "name": "client", + "tree": { + "$className": "DataModel", + + "ServerScriptService": { + "MarcSync": { + "$path": "src/MarcSync" + } + } + } +} \ No newline at end of file diff --git a/src/MarcSync/MarcSyncv0.5.lua b/src/MarcSync/MarcSyncv0.5.lua new file mode 100644 index 0000000..82235cd --- /dev/null +++ b/src/MarcSync/MarcSyncv0.5.lua @@ -0,0 +1,211 @@ + +local tokens = { + ["exampleToken"] = "" +} + + +-- DO NOT EDIT THE FOLLOWING LINES BELOW, UNLESS YOU KNOW WHAT YOU ARE DOING! + +local Utils = require(script.Parent.Utils) + +type Function = () -> () +type DefaultResultType = (success: boolean, errorMessage: string) -> () +type DefaultResult = { + ["onResult"]: { + ["Connect"]:DefaultResultType + } +} + +local HttpService = game:GetService("HttpService") + +local reqQueue = {} + +coroutine.wrap(function() + while wait(5) do + for i,v in pairs(reqQueue) do + local result; + local success, Error = pcall(function() result = v.method(v.arguments) end) + if not success and Error == "Number of requests exceeded limit" then + break + else + v.event:__Fire(result) + table.remove(reqQueue, i) + end + end + end +end) + +function signal() + local RBXSignal = {} + RBXSignal._bindableEvent = Instance.new("BindableEvent") + function RBXSignal:_Fire(...) + RBXSignal._bindableEvent:Fire(...) + end + function RBXSignal:Connect(handler: ({success: boolean, result: {}}) -> ({success: boolean, result: {}})) + if typeof(self) ~= "table" then error("Please use : instead of .") end + if not (type(handler) == "function") then + error(("connect(%s)"):format(typeof(handler)), 2) + end + RBXSignal._bindableEvent.Event:Connect(function(...) + handler(...) + end) + end + return RBXSignal +end + +local marcsync = {} +marcsync.__index = marcsync +marcsync.request = {} +function marcsync.new(token: string) + if not token then warn("Token not provided while creating a new MarcSync Object.") end + if not tokens[token] then warn("Token provided for creating a new MarcSync Object not Found in Token Table, using it as token instead.") else token = tokens[token] end + local self = setmetatable({}, marcsync) + self._token = token + self.request._parent = self + self.request.version._parent = self.request + self.request.database._parent = self.request + self.request.collection._parent = self.request + self.utils._parent = self + return self +end + +function marcsync:_checkInstallation() + if not self then error("Please Setup MarcSync before using MarcSync.") end + if not self._token then error("[MarcSync] Please set a Token before using MarcSync.") end + --print(HttpService.HttpEnabled) + --if not HttpService.HttpEnabled then error("Please Enable HTTPService in order to use MarcSync.") end +end + +function marcsync._errorHandler(RBXSignal: {}, Error: string) + spawn(function() + RBXSignal:_Fire({ + ["success"] = false, + ["errorMessage"] = Error + }) + end) + return {["onResult"] = RBXSignal} +end + +function marcsync:_requestHandler(result: {}, Error: string):{["onResult"]: {}} + if result == nil then return self._errorHandler(signal(), Error) end + local errorResult; + local signalevent; + if #result.Body == 0 and not result.Success then + errorResult = "HTTP "..result.StatusCode.." ("..result.StatusMessage..")" result = false + elseif not pcall(function() HttpService:JSONDecode(result.Body) end) then + error("Unexpected MarcSync Result.") + else + result = result.Body + signalevent = {["onResult"]=signal()} + end + spawn(function() + if not result then return end + signalevent.onResult:_Fire(result) + end) + return signalevent or self._errorHandler(signal(), errorResult) +end + +marcsync.request.version = {} +function marcsync.request.version:get(clientId: number?):{["success"]:boolean,["version"]:string,["update_server"]:string} + if typeof(self) ~= "table" then error("Please use : instead of .") end + if not self._parent then error("[MarcSync] Please set a Token before using MarcSync.") end + self._parent._parent:_checkInstallation() + local url = "" + if clientId then url = "/"..clientId end + local result = nil; + Utils.handleResponse(Utils.makeHTTPRequest("GET", "https://api.marcsync.dev/v0/utils/version"..url)).onResult:Connect(function(any) + result = any + end) + repeat + wait() + until result ~= nil + return result +end + +marcsync.request.collection = {} +function marcsync.request.collection:create(collectionName: string):typeof(require(script.Parent.Objects.Collection)) + if typeof(self) ~= "table" then error("Please use : instead of .") end + if not self._parent then error("[MarcSync] Please set a Token before using MarcSync.") end + self._parent._parent:_checkInstallation() + if not collectionName then error("No CollectionName Provided") end + local result = nil; + Utils.handleResponse(Utils.makeHTTPRequest("POST", "https://api.marcsync.dev/v0/collection/"..collectionName, {}, self._parent._parent._token)).onResult:Connect(function(any) + if not any["success"] then result = false return end + result = require(script.Parent.Objects.Collection)._new(collectionName, self._parent._parent._token) + end) + repeat + wait() + until result ~= nil + return result +end +function marcsync.request.collection:fetch(collectionName: string):typeof(require(script.Parent.Objects.Collection)) + if typeof(self) ~= "table" then error("Please use : instead of .") end + if not self._parent then error("[MarcSync] Please set a Token before using MarcSync.") end + self._parent._parent:_checkInstallation() + if not collectionName then error("No CollectionName Provided") end + local result = nil; + Utils.handleResponse(Utils.makeHTTPRequest("GET", "https://api.marcsync.dev/v0/collection/"..collectionName, {}, self._parent._parent._token)).onResult:Connect(function(any) + if not any["success"] then result = false return end + result = require(script.Parent.Objects.Collection)._new(collectionName, self._parent._parent._token) + end) + repeat + wait() + until result ~= nil + return result +end +function marcsync.request.collection:get(collectionName: string):typeof(require(script.Parent.Objects.Collection)) + if typeof(self) ~= "table" then error("Please use : instead of .") end + if not self._parent then error("[MarcSync] Please set a Token before using MarcSync.") end + self._parent._parent:_checkInstallation() + if not collectionName then error("No CollectionName Provided") end + return require(script.Parent.Objects.Collection)._new(collectionName, self._parent._parent._token) +end +marcsync.request.database = {} +function marcsync.request.database:fetch(databaseId: number):DefaultResult + if typeof(self) ~= "table" then error("Please use : instead of .") end + if not self._parent then error("[MarcSync] Please set a Token before using MarcSync.") end + self._parent._parent:_checkInstallation() + if not databaseId then error("No DatabaseId Provided") end + local result; + local success, Error = pcall(function() result = HttpService:RequestAsync({Url = "https://api.marcthedev.it/marcsync/v0/database/"..databaseId, Headers = {["Authorization"]=self._parent._parent._token}}) end) + return self._parent._parent:_requestHandler(result, Error) +end +function marcsync.request.database:delete(databaseId: number):DefaultResult + if typeof(self) ~= "table" then error("Please use : instead of .") end + if not self._parent then error("[MarcSync] Please set a Token before using MarcSync.") end + self._parent._parent:_checkInstallation() + if not databaseId then error("No DatabaseId Provided") end + local result; + local success, Error = pcall(function() result = HttpService:RequestAsync({Url = "https://api.marcthedev.it/marcsync/v0/database/"..databaseId, Headers = {["Authorization"]=self._parent._parent._token}}) end) + return self._parent._parent:_requestHandler(result, Error) +end +marcsync.utils = {} +function marcsync.utils.safeRequest(method: Function, arguments: {}) + --_checkInstallation() + + + --[[ + spawn(function() + local result; + local success, Error = pcall(function() result = method(arguments) end) + if not success and Error == "Number of requests exceeded limit" then + reqQueue[#reqQueue] = {["method"] = method, ["arguments"] = arguments, ["event"] = RBXSignal} + elseif success then + RBXSignal:__Fire(result) + end + end) + + return { ["onResult"] = RBXSignal} + --]] +end +function marcsync.utils.bulkRequest(methods: {}, safeReq: boolean?):{} + --_checkInstallation() + local returns = {} + for i,method in pairs(methods) do if safeReq then marcsync.safeRequest(method[1], method[2]).onResult:Connect(function(result) returns[i] = result end) else returns[i] = method[1](method[2]) end end + while #methods ~= #returns do + wait() + end + return returns +end + +return marcsync \ No newline at end of file diff --git a/src/MarcSync/Objects/Collection.lua b/src/MarcSync/Objects/Collection.lua new file mode 100644 index 0000000..746c570 --- /dev/null +++ b/src/MarcSync/Objects/Collection.lua @@ -0,0 +1,109 @@ +local Utils = require(script.Parent.Parent.Utils) +local Entry = require(script.Parent.Entry) + +local collection = {} +collection.__index = collection + +local filterSheme = { + ["values"]=typeof({ ["key"]=... }), + ["startsWith"]=typeof({ "key" }), + ["ignoreCases"]=typeof({ "key" }) +} + +function collection:insert(data:{key:any}):typeof(Entry)? + if typeof(self) ~= "table" then error("Please use : instead of .") end + if not self._collectionName then error("[MarcSync: Collection] Invalid Object created or trying to access an destroied object.") end + local result = nil; + Utils.handleResponse(Utils.makeHTTPRequest("POST", "https://api.marcsync.dev/v0/entries/"..self._collectionName, {["data"]=data}, self._token)).onResult:Connect(function(any) + if any["success"] and any["objectId"] then + data["_id"] = any["objectId"] + result = require(script.Parent.Entry)._new(self._collectionName, data, self._token) + return + end + result = any + end) + repeat + wait() + until result ~= nil + return result +end + +function collection:update(filters:typeof(filterSheme),data:{key:any}):{message:string?,errorMessage:string?,success:boolean,modifiedEntries:number?} + if typeof(self) ~= "table" then error("Please use : instead of .") end + if not self._collectionName then error("[MarcSync: Collection] Invalid Object created or trying to access an destroied object.") end + local result = nil; + if not filters.values then error("[MarcSync: Collection] Invalid arguments given for collection:select(). Expected filters.values, got nil") end + filters.values["_startsWith"] = filters.startsWith or {} + filters.values["_ignoreCases"] = filters.ignoreCases or {} + Utils.handleResponse(Utils.makeHTTPRequest("PUT", "https://api.marcsync.dev/v1/entries/"..self._collectionName, {["filters"]=filters.values,["data"]=data}, self._token)).onResult:Connect(function(any) + result = any + end) + repeat + wait() + until result ~= nil + return result +end + +function collection:select(filters:typeof(filterSheme),limit:number):{entries:{a:typeof(Entry)?,b:typeof(Entry)?,c:typeof(Entry)?}?,success:boolean,message:string?,errorMessage:string?} + if typeof(self) ~= "table" then error("Please use : instead of .") end + if not self._collectionName then error("[MarcSync: Collection] Invalid Object created or trying to access an destroied object.") end + local result = nil; + if not filters.values then error("[MarcSync: Collection] Invalid arguments given for collection:select(). Expected filters.values, got nil") end + filters.values["_startsWith"] = filters.startsWith or {} + filters.values["_ignoreCases"] = filters.ignoreCases or {} + Utils.handleResponse(Utils.makeHTTPRequest("PATCH", "https://api.marcsync.dev/v1/entries/"..self._collectionName.."?methodOverwrite=GET", {["filters"]=filters.values, ["limit"]=limit}, self._token)).onResult:Connect(function(any) + if any["success"] and any["entries"] then + local _result = {["entries"]={},["success"]=true} + for index,entry in pairs(any["entries"]) do + _result["entries"][index] = require(script.Parent.Entry)._new(self._collectionName, entry, self._token) + end + result = _result + return + end + result = any + end) + repeat + wait() + until result ~= nil + return result +end + +function collection:delete(filters:typeof(filterSheme)):{message:string?,errorMessage:string?,success:boolean,deletedEntries:number?} + if typeof(self) ~= "table" then error("Please use : instead of .") end + if not self._collectionName then error("[MarcSync: Collection] Invalid Object created or trying to access an destroied object.") end + local result = nil; + if not filters.values then error("[MarcSync: Collection] Invalid arguments given for collection:select(). Expected filters.values, got nil") end + filters.values["_startsWith"] = filters.startsWith or {} + filters.values["_ignoreCases"] = filters.ignoreCases or {} + Utils.handleResponse(Utils.makeHTTPRequest("DELETE", "https://api.marcsync.dev/v1/entries/"..self._collectionName, {["filters"]=filters.values}, self._token)).onResult:Connect(function(any) + result = any + end) + repeat + wait() + until result ~= nil + return result +end + +function collection:drop():{success:boolean,message:string?,errorMessage:string?} + if typeof(self) ~= "table" then error("Please use : instead of .") end + if not self._collectionName then error("[MarcSync: Collection] Invalid Object created or trying to access an destroied object.") end + local result = nil; + Utils.handleResponse(Utils.makeHTTPRequest("DELETE", "https://api.marcsync.dev/v0/collection/"..self._collectionName, {}, self._token)).onResult:Connect(function(any) + result = any + end) + repeat + wait() + until result ~= nil + return result +end + +collection._collectionName = nil + +function collection._new(_collectionName: string, _token: string):typeof(collection) + local self = setmetatable({}, collection) + self._collectionName = _collectionName + self._token = _token + return self +end + +return collection \ No newline at end of file diff --git a/src/MarcSync/Objects/Entry.lua b/src/MarcSync/Objects/Entry.lua new file mode 100644 index 0000000..0524f71 --- /dev/null +++ b/src/MarcSync/Objects/Entry.lua @@ -0,0 +1,62 @@ +local Utils = require(script.Parent.Parent.Utils) + +local entry = {} +entry.__index = entry + +function entry:getValue(value:string):any + if typeof(self) ~= "table" then error("Please use : instead of .") end + if not value then return nil end + return self._values[value] +end + +function entry:getValues():{} + if typeof(self) ~= "table" then error("Please use : instead of .") end + return self._values +end + +function entry:update(newData:{key:any}):{success:boolean,errorMessage:string?,message:string?,modifiedEntries:IntValue?} + if typeof(self) ~= "table" then error("Please use : instead of .") end + local result = nil; + Utils.handleResponse(Utils.makeHTTPRequest("PUT", "https://api.marcsync.dev/v0/entries/"..self._tableId, {["filters"]={["_id"]=self._objectId},["data"]=newData}, self._token)).onResult:Connect(function(any) + if any["success"] and any["modifiedEntries"] and any["modifiedEntries"] > 0 then + for i,v in pairs(newData) do + self._values[i] = v + end + end + result = any + end) + repeat + wait() + until result ~= nil + return result +end + +function entry:delete():{success:boolean,errorMessage:string?,message:string?,deletedEntries:IntValue?} + if typeof(self) ~= "table" then error("Please use : instead of .") end + local result = nil; + Utils.handleResponse(Utils.makeHTTPRequest("DELETE", "https://api.marcsync.dev/v0/entries/"..self._tableId, {["filters"]={["_id"]=self._objectId}}, self._token)).onResult:Connect(function(any) + if any["success"] and any["deletedEntries"] then + if any["deletedEntries"] < 1 then result = false return end + spawn(function() + self = nil + end) + end + result = any + end) + repeat + wait() + until result ~= nil + return result +end + +function entry._new(_tableId:string, _values:{}, _token:string):typeof(entry) + if not _tableId or not _values or not _values["_id"] then error("[MarcSync: Entry] Tried creating invalid Entry Object.") end + local self = setmetatable({}, entry) + self._tableId = _tableId + self._values = _values + self._objectId = _values["_id"] + self._token = _token + return self +end + +return entry \ No newline at end of file diff --git a/src/MarcSync/Types/MARCSYNC_COLLECTION.lua b/src/MarcSync/Types/MARCSYNC_COLLECTION.lua new file mode 100644 index 0000000..a1f19fd --- /dev/null +++ b/src/MarcSync/Types/MARCSYNC_COLLECTION.lua @@ -0,0 +1 @@ +return require(script.Parent.Parent.Parent.Objects.Collection) \ No newline at end of file diff --git a/src/MarcSync/Types/MARCSYNC_ENTRY.lua b/src/MarcSync/Types/MARCSYNC_ENTRY.lua new file mode 100644 index 0000000..04ee486 --- /dev/null +++ b/src/MarcSync/Types/MARCSYNC_ENTRY.lua @@ -0,0 +1 @@ +return require(script.Parent.Parent.Parent.Objects.Entry) \ No newline at end of file diff --git a/src/MarcSync/Types/MARCSYNC_UTILS.lua b/src/MarcSync/Types/MARCSYNC_UTILS.lua new file mode 100644 index 0000000..03630f8 --- /dev/null +++ b/src/MarcSync/Types/MARCSYNC_UTILS.lua @@ -0,0 +1 @@ +return require(script.Parent.Parent) \ No newline at end of file diff --git a/src/MarcSync/Utils.lua b/src/MarcSync/Utils.lua new file mode 100644 index 0000000..fd33163 --- /dev/null +++ b/src/MarcSync/Utils.lua @@ -0,0 +1,64 @@ +local HttpService = game:GetService("HttpService") + +function errorHandler(RBXSignal: {}, result: any) + local Error; + if typeof(result) == typeof({}) and result["message"] then + Error = result["message"] + elseif typeof(result) == typeof("") then + Error = result + else + Error = "An Unexpected Error occoured." + end + spawn(function() + RBXSignal:_Fire({ + ["success"] = false, + ["errorMessage"] = Error + }) + end) + return {["onResult"] = RBXSignal} +end + +local utils = {} +function utils._signal() + local RBXSignal = {} + RBXSignal._bindableEvent = Instance.new("BindableEvent") + function RBXSignal:_Fire(...) + RBXSignal._bindableEvent:Fire(...) + end + function RBXSignal:Connect(handler: ({success: boolean, result: {}}) -> ({success: boolean, result: {}})) + if typeof(self) ~= "table" then error("Please use : instead of .") end + if not (type(handler) == "function") then + error(("connect(%s)"):format(typeof(handler)), 2) + end + RBXSignal._bindableEvent.Event:Connect(function(...) + handler(...) + end) + end + return RBXSignal +end +function utils.handleResponse(result: any, error: boolean, signal: RBXScriptSignal?) + if error then return result end + spawn(function() + signal:_Fire(result) + end) + return {["onResult"] = signal} +end + +function utils.makeHTTPRequest(method: string, url: string, body: {}, authorization: string):{["success"]: boolean, ["message"]: string} + local result; + local success = pcall(function() + if body then body = HttpService:JSONEncode(body) end + if (method == "GET" or method == "HEAD") then + result = HttpService:JSONDecode(HttpService:RequestAsync({Method=method, Url=url, Headers={["Authorization"]=authorization,["Content-Type"]="application/json"}})["Body"]) + else + result = HttpService:JSONDecode(HttpService:RequestAsync({Method=method, Url=url, Headers={["Authorization"]=authorization,["Content-Type"]="application/json"}, Body=body})["Body"]) + end + end) + if success and result and result["success"] then + if result["warning"] then warn('[MarcSync HTTPRequest Handler] MarcSync HTTP Request returned warning for URL "'..url..'" with body: "'..HttpService:JSONEncode(body)..'": '..result["warning"]) end + return result, false, utils._signal() + end + return errorHandler(utils._signal(), result), true +end + +return utils \ No newline at end of file