func main(args)
{
var dir = ".";
if(args.Length > 1)
{
dir = args[1];
}
DB.Init(dir);
const timer = new Timer(()=>{
const time = DateTime.NowEpoch ?? 0;
DB.Lock();
const db = DB.Open();
Sqlite.Exec(db, "DELETE FROM sessions WHERE expires < {time} AND expires > 0;");
Sqlite.Close(db);
DB.Unlock();
},900000);
/*
PUT /api/v1/upload Authorization Bearer
POST /api/v1/login Json object with email and password returns json object with either 200 for success {"token": "TOKEN_VAL"} or non 2XX if fails {"reason": "SOME ERROR"}
POST /api/v1/logout use Authorization Bearer
GET /api/v1/latest?name=PackageName returns 200 OK with json {"version":"1.0.0.0-dev"} if it succeeds if it fails returns a failing status code with {"reason": "SOME ERROR"}
GET /api/v1/download?name=PackageName&version=1.0.0.0-prod returns 200 OK with package bytes or 404 if doesn't exist
GET /api/v1/search?q=SomeQuery&offset=&limit= returns 200 OK with json of packages {"packages": [{"name": "pkgName","version": "latestVersion", ...}]} or non success status code
*/
Net.Http.ListenSimpleWithLoop((ctx)=>{
if(ctx.Path == "/check_email")
{
ctx.WithMimeType("text/html").SendText(Pages.CheckEmail(ctx));
}
if(ctx.Path == "/reserved_prefixes")
{
ctx.WithMimeType("text/html").SendText(Pages.ReservedPrefixes(ctx));
return true;
}
if(ctx.Path == "/admin")
{
ctx.WithMimeType("text/html").SendText(Pages.Admin(ctx));
return true;
}
if(ctx.Path == "/admin_register")
{
ctx.WithMimeType("text/html").SendText(Pages.AdminRegister(ctx));
return true;
}
if(ctx.Path == "/admin_accounts")
{
ctx.WithMimeType("text/html").SendText(Pages.AdminAccount(ctx));
return true;
}
if(ctx.Path == "/package")
{
var name = ctx.QueryParams.TryGetFirst("name");
if(TypeOf(name) != "String") name = "";
ctx.WithMimeType("text/html").SendText(Pages.Package(ctx,name));
return true;
}
if(ctx.Path == "/package_docs")
{
ctx.WithMimeType("text/html").SendText(Pages.PackageDocs(ctx));
return true;
}
if(ctx.Path == "/packages")
{
ctx.WithMimeType("text/html").SendText(Pages.Packages(ctx));
return true;
}
if(ctx.Path == "/api/v1/latest")
{
var name = ctx.QueryParams.TryGetFirst("name");
if(TypeOf(name) != "String") name = "";
var version = DB.GetLatestVersion(name);
if(version != null)
{
ctx.WithMimeType("application/json").SendText(Json.Encode({version}));
return true;
}
}
if(ctx.Path == "/api/v1/search")
{
var filter = "";
var type = ctx.QueryParams.TryGetFirst("type");
if(TypeOf(type) != "String") type = "";
var types = type.Length > 0 ? type.Split(",") : [];
var pluginHost = ctx.QueryParams.TryGetFirst("pluginHost");
if(TypeOf(pluginHost) == "String")
{
filter += $" AND v.pluginHost = {Sqlite.Escape(pluginHost)}";
}
if(types.Length > 0)
{
filter += " AND (";
var first = true;
each(var item : types)
{
if(!first) {
filter += " OR ";
}
filter += $"v.type = {Sqlite.Escape(item)}";
first=false;
}
filter += ")";
}
var q = ctx.QueryParams.TryGetFirst("q");
if(TypeOf(q) != "String") q = "";
var offset = ParseLong(ctx.QueryParams.TryGetFirst("offset"));
if(TypeOf(offset) != "Long") offset=0;
var limit = ParseLong(ctx.QueryParams.TryGetFirst("limit"));
if(TypeOf(limit) != "Long") limit = 20;
if(limit <= 0) limit = 20;
var res = DB.QueryPackages(q, offset*limit, limit, undefined, filter);
if(TypeOf(res) != "List")
{
ctx.StatusCode=500;
ctx.SendText("Packages not a list");
return true;
}
else
{
ctx.WithMimeType("application/json").SendText(Json.Encode({packages=res}));
return true;
}
}
if(ctx.Path == "/change_motto")
{
if(ctx.Method == "POST")
{
var motto = ctx.QueryParams.TryGetFirst("motto");
if(motto == null) motto = "";
var session = DB.GetSession(ctx);
if(session == null)
{
ctx.StatusCode = 401;
ctx.SendText("
You are not logged in
");
return true;
}
var csrf = ctx.QueryParams.TryGetFirst("csrf");
var result = { Success=false, Reason = "Invalid CSRF"};
if(DB.VerifyCSRF(session,csrf))
{
var userId = DB.GetUserIdFromSession(session).accountId;
var url = DB.ChangeMotto(userId,motto);
ctx.StatusCode = 303;
ctx.ResponseHeaders.SetValue("Location", url);
ctx.WithMimeType("text/html").SendText(
Redirecting
Click here if it does not redirect
);
return true;
}
}
}
if(ctx.Path == "/upload")
{
if(ctx.Method == "GET")
{
ctx.WithMimeType("text/html").SendText(Pages.Upload(ctx));
return true;
}
else if(ctx.Method == "POST")
{
if(ctx.NeedToParseFormData)
{
var filePath = DB.working / "Temp" / $"{DB.GetUniqueNumber()}.crvm";
var hasFile=false;
var strm = FS.Local.OpenFile(filePath,"wb");
ctx.ParseFormData((mime,filename,name)=>{
if(name == "package")
{
if(hasFile) return new MemoryStream(true);
hasFile=true;
return strm;
}
else
return new MemoryStream(true);
});
strm.Close();
var session = DB.GetSession(ctx);
if(session == null)
{
if(FS.Local.FileExists(filePath))
FS.Local.DeleteFile(filePath);
ctx.StatusCode = 401;
ctx.SendText("You are not logged in
");
return true;
}
var csrf = ctx.QueryParams.TryGetFirst("csrf");
var result = { Success=false, Reason = "Invalid CSRF"};
if(!DB.VerifyCSRF(session,csrf))
{
var userId = DB.GetUserIdFromSession(session).accountId;
result = DB.UploadPackage(userId, filePath);
}
if(FS.Local.FileExists(filePath))
FS.Local.DeleteFile(filePath);
if(result.Success)
{
ctx.StatusCode = 303;
ctx.ResponseHeaders.SetValue("Location", "/");
ctx.WithMimeType("text/html").SendText(
Redirecting
Click here if it does not redirect
);
}
else
{
ctx.StatusCode = 400;
ctx.SendText(result.Reason);
}
return true;
}
}
}
if(ctx.Path == "/api")
{
ctx.WithMimeType("text/html").SendText(Pages.API.Index());
return true;
}
if(ctx.Path == "/api-v1")
{
ctx.WithMimeType("text/html").SendText(Pages.API.V1());
return true;
}
if(ctx.Path == "/api/v1/versions")
{
var name = ctx.QueryParams.TryGetFirst("name");
if(TypeOf(name) == "String")
{
var versions = DB.GetPackageVersions(name);
ctx.WithMimeType("application/json").SendJson({
success=true,
versions
});
return true;
}
ctx.WithMimeType("application/json").SendJson({
success=false
});
return true;
}
if(ctx.Path == "/delete_packages")
{
if(ctx.Method == "GET")
{
ctx.WithMimeType("text/html").SendText(Pages.DeletePackages(ctx));
return true;
}
else if(ctx.Method == "POST")
{
var packages = ctx.QueryParams.TryGetFirst("packages");
var email = ctx.QueryParams.TryGetFirst("email");
var password = ctx.QueryParams.TryGetFirst("password");
var msg = DB.DeletePackages(email,password, packages);
var html = {msg}
;
ctx.WithMimeType("text/html").SendText(Shell(msg,[], html));
}
}
if(ctx.Path == "/api/v1/upload")
{
if(ctx.Method == "PUT")
{
var session = DB.GetSessionFromBearer(ctx);
if(session == null)
{
ctx.StatusCode=401;
ctx.SendJson({
reason = "You are not logged in"
});
return true;
}
var userId = DB.GetUserIdFromSession(session).accountId;
var filePath = DB.working / "Temp" / $"{DB.GetUniqueNumber()}.crvm";
var strm = FS.Local.OpenFile(filePath,"wb");
ctx.ReadStream(strm);
strm.Close();
var result = DB.UploadPackage(userId, filePath);
if(result.Success)
{
ctx.StatusCode = 204;
ctx.ResponseHeaders.SetValue("Content-Length","0");
ctx.WriteHeaders();
return true;
}
else {
ctx.StatusCode = 400;
ctx.SendJson({reason = result.Reason});
return true;
}
}
else {
ctx.StatusCode = 400;
ctx.SendJson({
reason = $"Expected PUT method got {ctx.Method}"
});
}
return true;
}
if(ctx.Path == "/api/v1/login")
{
if(ctx.Method == "POST")
{
var json = ctx.ReadJson();
if(TypeOf(json) != "Dictionary" || TypeOf(json.email) != "String" || TypeOf(json.password) != "String") {
ctx.StatusCode = 400;
ctx.SendJson({
reason = "Expected a Json Dictionary, with the email and password"
});
return true;
}
var accountId = DB.GetAccountId(json.email, json.password);
if(accountId == -1)
{
ctx.StatusCode = 401;
ctx.SendJson({
reason = "Invalid credentials"
});
return true;
}
ctx.SendJson({
token = DB.CreateSession(accountId,false, TypeIsString(json.name) ? json.name : "CrossLang Shell")
});
return true;
}
else {
ctx.StatusCode = 400;
ctx.SendJson({
reason = $"Expected POST method got {ctx.Method}"
});
}
return true;
}
if(ctx.Path == "/api/v1/package_icon.png")
{
var name = ctx.QueryParams.TryGetFirst("name");
var version = ctx.QueryParams.TryGetFirst("version");
ctx.ResponseHeaders.SetValue("Content-Type", "image/png");
ctx.SendBytes(DB.GetPackageIcon(name,version));
return true;
}
if(ctx.Path == "/api/v1/download")
{
var name = ctx.QueryParams.TryGetFirst("name");
var version = ctx.QueryParams.TryGetFirst("version");
if(TypeOf(name) != "String") name = "";
if(TypeOf(version) != "String") version = "";
var file = DB.working / "Packages" / name / $"{name}-{version}.crvm";
if(FS.Local.FileExists(file) && name.Length > 0 && version.Length > 0)
{
var strm = FS.Local.OpenFile(file,"rb");
if(strm != null)
{
ctx.WithMimeType("application/crvm").WithContentDisposition($"{name}-{version}.crvm",false).SendStream(strm);
strm.Close();
return true;
}
}
return false;
}
if(ctx.Path == "/api/v1/logout")
{
ctx.WithMimeType("application/json").SendText({
Success=DB.DestroySession(DB.GetSessionFromBearer(ctx))
}.ToString());
}
if(ctx.Path == "/logout")
{
if(DB.DestroySession(DB.GetSession(ctx)))
{
ctx.StatusCode = 302;
ctx.ResponseHeaders.SetValue("Location", "/");
ctx.WithMimeType("text/html").SendText(
Redirecting
Click here if it does not redirect
);
return true;
}
else {
ctx.WithMimeType("text/html").SendText(Shell("Not logged in",[],Not logged in
));
return true;
}
}
if(ctx.Path == "/login")
{
if(ctx.Method == "GET")
{
ctx.WithMimeType("text/html").SendText(Pages.Login(ctx));
return true;
}
else if(ctx.Method == "POST")
{
var email = ctx.QueryParams.TryGetFirst("email");
if(TypeOf(email) != "String")
{
ctx.StatusCode = 400;
ctx.SendText("You forgot the email buddy
");
return true;
}
var password = ctx.QueryParams.TryGetFirst("password");
if(TypeOf(password) != "String")
{
ctx.StatusCode = 400;
ctx.SendText("You forgot the password buddy
");
return true;
}
var accountId = DB.GetAccountId(email, password);
if(accountId == -1)
{
ctx.StatusCode = 400;
ctx.SendText(Shell("Invalid credentials",[],"Invalid credentials
"));
return true;
}
ctx.StatusCode = 303;
ctx.ResponseHeaders.SetValue("Location", "/");
const browser="Browser";
const now = DateTime.NowEpoch??0;
ctx.ResponseHeaders.SetValue("Set-Cookie", $"Session={DB.CreateSession(accountId,true, browser)}; SameSite=Strict; Expires={new DateTime(now+DB.Expires).ToHttpDate()}");
ctx.WithMimeType("text/html").SendText(
Redirecting
Click here if it does not redirect
);
return true;
}
}
if(ctx.Path == "/verify")
{
var code = ctx.QueryParams.TryGetFirst("code");
if(TypeOf(code) == "String")
{
var res = DB.VerifyEmail(code);
if(res.Success)
{
ctx.StatusCode = 302;
ctx.ResponseHeaders.SetValue("Location", "/");
ctx.WithMimeType("text/html").SendText(
Redirecting
Click here if it does not redirect
);
return true;
}
else {
ctx.StatusCode=401;
ctx.WithMimeType("text/html").SendText(Pages.VerificationFailed(res.Reason));
return true;
}
}
else {
ctx.WithMimeType("text/html").SendText(Pages.VerificationFailed("Requires query parameter code"));
return true;
}
}
if(ctx.Path == "/new_password")
{
if(ctx.Method == "GET")
{
ctx.WithMimeType("text/html").SendText(Pages.NewPassword(ctx));
return true;
}
else if(ctx.Method == "POST")
{
var code = ctx.QueryParams.TryGetFirst("code");
var password = ctx.QueryParams.TryGetFirst("password");
var confirm = ctx.QueryParams.TryGetFirst("confirm");
if(TypeOf(code) == "String" && TypeOf(password) == "String" && TypeOf(confirm) == "String")
{
var res = DB.UnforgetPassword(code,password,confirm);
if(res.Success)
{
ctx.StatusCode = 303;
ctx.ResponseHeaders.SetValue("Location", "/");
ctx.WithMimeType("text/html").SendText(
Redirecting
Click here if it does not redirect
);
return true;
}
else {
ctx.StatusCode=401;
ctx.WithMimeType("text/html").SendText(Pages.ResetPasswordFailed(res.Reason));
return true;
}
}
else {
ctx.StatusCode = 400;
ctx.WithMimeType("text/html").SendText(Pages.ResetPasswordFailed("Invalid State"));
return true;
}
}
}
if(ctx.Path == "/forgot_password")
{
if(ctx.Method == "GET")
{
ctx.WithMimeType("text/html").SendText(Pages.ForgotPassword(ctx));
return true;
}
else if(ctx.Method == "POST")
{
var email = ctx.QueryParams.TryGetFirst("email");
if(TypeOf(email) != "String")
{
ctx.StatusCode = 400;
ctx.SendText("You forgot the email buddy
");
return true;
}
var res = DB.ForgotPassword(email);
if(!res.Success)
{
ctx.StatusCode = 400;
ctx.SendText($"Error: {Net.Http.HtmlEncode(res.Reason)}
");
return true;
}
ctx.StatusCode = 303;
ctx.ResponseHeaders.SetValue("Location", res.Redirect);
ctx.WithMimeType("text/html").SendText(
Redirecting
Click here if it does not redirect
);
return true;
}
}
if(ctx.Path == "/signup")
{
if(ctx.Method == "GET")
{
ctx.WithMimeType("text/html").SendText(Pages.Signup(ctx));
return true;
}
else if(ctx.Method == "POST")
{
var email = ctx.QueryParams.TryGetFirst("email");
if(TypeOf(email) != "String")
{
ctx.StatusCode = 400;
ctx.SendText("You forgot the email buddy
");
return true;
}
var displayName = ctx.QueryParams.TryGetFirst("displayName");
if(TypeOf(displayName) != "String")
{
ctx.StatusCode = 400;
ctx.SendText("You forgot the displayName buddy
");
return true;
}
var password = ctx.QueryParams.TryGetFirst("password");
if(TypeOf(password) != "String")
{
ctx.StatusCode = 400;
ctx.SendText("You forgot the password buddy
");
return true;
}
var passwordconfirm = ctx.QueryParams.TryGetFirst("passwordconfirm");
if(TypeOf(passwordconfirm) != "String")
{
ctx.StatusCode = 400;
ctx.SendText("You forgot the passwordconfirm buddy
");
return true;
}
if(password != passwordconfirm)
{
ctx.StatusCode = 400;
ctx.SendText("The passwords do not match
");
return true;
}
var res = DB.CreateUser(email, displayName, password);
if(!res.Success)
{
ctx.StatusCode = 400;
ctx.SendText(Shell("Error",[],$"Error: {Net.Http.HtmlEncode(res.Reason)}
"));
return true;
}
ctx.StatusCode = 303;
ctx.ResponseHeaders.SetValue("Location", res.Redirect);
ctx.WithMimeType("text/html").SendText(
Redirecting
Click here if it does not redirect
);
return true;
}
}
if(ctx.Path == "/account")
{
ctx.WithMimeType("text/html").SendText(Pages.Account(ctx));
return true;
}
if(ctx.Path == "/sessions")
{
ctx.WithMimeType("text/html").SendText(Pages.Sessions(ctx));
return true;
}
if(ctx.Path == "/create_session")
{
ctx.WithMimeType("text/html").SendText(Pages.CreateSession(ctx));
return true;
}
if(ctx.Path == "/session")
{
ctx.WithMimeType("text/html").SendText(Pages.Session(ctx));
return true;
}
if(ctx.Path == "/css/bootstrap.min.css")
{
ctx.WithMimeType("text/css").SendBytes(embed("css/bootstrap.min.css"));
return true;
}
if(ctx.Path == "/css/bootstrap.min.css.map")
{
ctx.WithMimeType("application/json").SendBytes(embed("css/bootstrap.min.css.map"));
return true;
}
if(ctx.Path == "/js/bootstrap.min.js")
{
ctx.WithMimeType("text/javascript").SendBytes(embed("js/bootstrap.min.js"));
return true;
}
if(ctx.Path == "/js/bootstrap.min.js.map")
{
ctx.WithMimeType("application/json").SendBytes(embed("js/bootstrap.min.js.map"));
return true;
}
if(ctx.Path == "/favicon.ico")
{
ctx.WithMimeType("image/x-icon").SendBytes(embed("favicon.ico"));
return true;
}
if(ctx.Path == "/")
{
ctx.WithMimeType("text/html").SendText(Pages.Index(ctx));
return true;
}
return false;
},DB.Port);
timer.Callback = null;
}