Get more done on packageserver

This commit is contained in:
2025-07-12 03:33:37 -05:00
parent 3ccb517aed
commit 35960d5db0
15 changed files with 441 additions and 78 deletions

View File

@@ -49,6 +49,9 @@ func DB.LoginButton(ctx,active,$accountPage)
func DB.CanUploadPackagePrefix(userId, packageName)
{
var prefix = packageName.Split(".",true,2);
if(prefix.Length >= 1)
prefix = prefix[0];
else return false;
DB.Lock();
var dbCon = DB.Open();
var exec = Sqlite.Exec(dbCon, $"SELECT * FROM reserved_prefixes WHERE prefix = {Sqlite.Escape(prefix)};");
@@ -158,7 +161,7 @@ func DB.UpdateVersion(pkgInfo)
//CREATE TABLE IF NOT EXISTS versions (id INTEGER PRIMARY KEY AUTOINCREMENT, packageId INTEGER, version INTEGER, description TEXT, type TEXT, maintainer TEXT, homepage TEXT, repo TEXT, license TEXT);
//VALUES ({pkgId},{version},{Sqlite.Escape(description)},{Sqlite.Escape(type)},{Sqlite.Escape(maintainer)},{Sqlite.Escape(homepage)},{Sqlite.Escape(repo)},{Sqlite.Escape(license)});
Sqlite.Exec(dbCon,$"UPDATE versions SET description = {Sqlite.Escape(description)}, type = {Sqlite.Escape(type)}, maintainer = {Sqlite.Escape(maintainer)}, homepage = {Sqlite.Escape(homepage)}, repo = {Sqlite.Escape(repo)}, license = {Sqlite.Escape(license)}, uploadTime = {Time.Now} WHERE packageId = {pkgId} AND version = {version};");
Sqlite.Exec(dbCon,$"UPDATE versions SET description = {Sqlite.Escape(description)}, type = {Sqlite.Escape(type)}, maintainer = {Sqlite.Escape(maintainer)}, homepage = {Sqlite.Escape(homepage)}, repo = {Sqlite.Escape(repo)}, license = {Sqlite.Escape(license)}, uploadTime = {DateTime.NowEpoch} WHERE packageId = {pkgId} AND version = {version};");
Sqlite.Close(dbCon);
DB.Unlock();
@@ -196,7 +199,7 @@ func DB.AddVersion(pkgInfo)
//CREATE TABLE IF NOT EXISTS versions (id INTEGER PRIMARY KEY AUTOINCREMENT, packageId INTEGER, version INTEGER, description TEXT, type TEXT, maintainer TEXT, homepage TEXT, repo TEXT, license TEXT);
Sqlite.Exec(dbCon,$"INSERT INTO versions (packageId,version,description,type,maintainer,homepage,repo,license,uploadTime) VALUES ({pkgId},{version},{Sqlite.Escape(description)},{Sqlite.Escape(type)},{Sqlite.Escape(maintainer)},{Sqlite.Escape(homepage)},{Sqlite.Escape(repo)},{Sqlite.Escape(license)},{Time.Now});");
Sqlite.Exec(dbCon,$"INSERT INTO versions (packageId,version,description,type,maintainer,homepage,repo,license,uploadTime) VALUES ({pkgId},{version},{Sqlite.Escape(description)},{Sqlite.Escape(type)},{Sqlite.Escape(maintainer)},{Sqlite.Escape(homepage)},{Sqlite.Escape(repo)},{Sqlite.Escape(license)},{DateTime.NowEpoch});");
Sqlite.Close(dbCon);
DB.Unlock();
@@ -294,7 +297,19 @@ func DB.GetUserIdFromSession(session)
return -1;
}
func DB.GetSessionFromBearer(ctx)
{
var auth = ctx.RequestHeaders.TryGetFirst("Authorization");
if(TypeOf(auth) == "String")
{
auth=auth.Split(" ",true,2);
if(auth.Length < 2) return null;
if(auth[0] != "Bearer") return null;
var uid = DB.GetUserIdFromSession(auth[1]);
if(uid != -1) return auth[1];
}
return null;
}
func DB.GetSession(ctx)
{
var cookie = ctx.RequestHeaders.TryGetFirst("Cookie");
@@ -337,7 +352,7 @@ func DB.CreateCSRF(ctx)
var csrf = Crypto.Base64Encode(Crypto.RandomBytes(32, "CPKG"));
var expires = Time.Now + 600;
var expires = DateTime.NowEpoch + 600;
DB.Lock();
DB.csrf.Add({
Token = csrf,
@@ -358,7 +373,7 @@ func DB.VerifyCSRF(session,csrf)
if(csrf.Length == 0) return false;
var retVal = false;
var time = Time.Now;
var time = DateTime.NowEpoch;
DB.Lock();
var _oldCSRF = DB.csrf;
DB.csrf = [];
@@ -661,7 +676,6 @@ func DB.QueryPackages(q, offset, limit)
DB.Lock();
var dbCon = DB.Open();
var res = Sqlite.Exec(dbCon, sql);
Sqlite.Close(dbCon);
DB.Unlock();

View File

@@ -1,11 +1,10 @@
func Components.Package(name,version,owner,upload,desc)
{
var image = $"./api/v1/package_icon.png?name={Net.Http.UrlEncode(name)}&version={Net.Http.UrlEncode(version)}";
var pkgUrl = $"./package?name={Net.Http.UrlEncode(name)}";
return <li>
<img width={"64"} height={"64"} src={image} alt={"icon"}>
<a href={pkgUrl}>{name}</a> | <span>By: {owner}</span>
<a href={pkgUrl}>{name}</a> | <span>By: <a href={$"./account?name={owner}"}>{owner}</a></span>
<br>
<span>Updated: {upload} | Latest version: {version}</span>
<p>{desc}</p>

View File

@@ -1,16 +1,24 @@
func Shell(title,pages,body)
{
var shell=
<html lang={"en"}>
<html lang={"en"} data-bs-theme="dark">
<head>
<meta charset={"UTF-8"}>
<meta name={"viewport"} content={"width=device-width, initial-scale=1.0"}>
<link rel={"stylesheet"} href={"./css/bootstrap.min.css"}>
<title>CPKG - {title}</title>
<noscript>
<style>
.navbar-toggler {"{"}
display:none;
}
</style>
</noscript>
</head>
<body>
<header>
<nav class={"navbar navbar-expand-lg"} style={"background-color: #ffff00;"}>
<nav class={"navbar navbar-expand-lg"} data-bs-theme="light" style={"background-color: orange;"}>
<div class={"container-fluid"}>
<a class={"navbar-brand"} href={"./"}>CPKG</a>
<button class={"navbar-toggler"} type={"button"} data-bs-toggle={"collapse"} data-bs-target={"#navbarNav"} aria-controls={"navbarNav"} aria-expanded={"false"} aria-label={"Toggle navigation"}>
@@ -33,7 +41,24 @@ func Shell(title,pages,body)
</ul>
</div>
</div>
<noscript>
<ul class={"d-none d-xs-block d-sm-block d-md-block d-lg-none navbar-nav"}>
<each(var item : pages)>
<li class={"nav-item"}>
<if(item.active)>
<true>
<a class={"nav-link active"} aria-current={"page"} href={item.route}>{item.text}</a>
</true>
<false>
<a class={"nav-link"} href={item.route}>{item.text}</a>
</false>
</if>
</li>
</each>
</ul>
</noscript>
</nav>
</header>
<br>

View File

@@ -81,8 +81,7 @@ func Pages.Account(ctx)
<if(TypeOf(user) == "Dictionary")>
<true>
<h1>{user.accountName}</h1>
<a href={$"./account_packages?name={Net.Http.UrlEncode(name)}"}>Packages</a>
<a href={$"./account_packages?name={Net.Http.UrlEncode(name)}"}>Packages</a>|<a href={$"./reserved_prefixes?name={Net.Http.UrlEncode(name)}"}>Reserved Prefixes</a>
<if(active.active)>
<true>

View File

@@ -22,6 +22,8 @@ func Pages.Index(ctx)
<input type={"search"} name={"q"} class={"form-control"} placeholder={"Search for packages..."} aria-label={"Search for packages..."} aria-describedby={"button-search"}>
<button class={"btn btn-outline-secondary"} type={"submit"} id={"button-search"}>Search</button>
</div>
<a href="./api">API</a>
</div>
<div class={"col"}>

View File

@@ -1,6 +1,7 @@
func Pages.Package(ctx,name)
{
var package = DB.GetPackageVersions(name);
var pages = [
{
@@ -15,7 +16,34 @@ func Pages.Package(ctx,name)
},
DB.LoginButton(ctx,false)
];
var installCmd = "";
if(package.Length > 0)
{
switch(package[0].type)
{
case "lib":
case "compiler_tool":
installCmd = $"crosslang add-dependency {name}";
break;
case "console":
installCmd = $"crosslang install-console {name} --version={package[0].version}";
break;
case "app":
installCmd = $"crosslang install-app {name} --version={package[0].version}";
break;
case "tool":
installCmd = $"crosslang install-tool {name} --version={package[0].version}";
break;
case "template":
installCmd = $"crosslang install-template {name} --version={package[0].version}";
break;
default:
installCmd = "No instructions to install sorry.";
break;
}
}
var html = <if(package.Length > 0)><true><section class={"container"}>
@@ -29,29 +57,33 @@ func Pages.Package(ctx,name)
<li class={"nav-item"} role={"presentation"}>
<button class={"nav-link active"} id={"home-tab"} data-bs-toggle={"tab"} data-bs-target={"#home-tab-pane"} type={"button"} role={"tab"} aria-controls={"home-tab-pane"} aria-selected={"true"}>CLI</button>
</li>
<if(package[0].type == "lib" || package[0].type == "compile_tool")>
<true>
<li class={"nav-item"} role={"presentation"}>
<button class={"nav-link"} id={"profile-tab"} data-bs-toggle={"tab"} data-bs-target={"#profile-tab-pane"} type={"button"} role={"tab"} aria-controls={"profile-tab-pane"} aria-selected={"false"}>cross.json</button>
</li>
</true>
</if>
</ul>
<div class={"tab-content"} id={"myTabContent"}>
<div class={"tab-pane fade show active"} id={"home-tab-pane"} role={"tabpanel"} aria-labelledby={"home-tab"} tabindex={"0"}><div class={"card"}>
<div class={"card-body"}>
<if(package[0].type == "lib" || package[0].type == "compile_tool")>
<true>
crosslang add-dependency {name}
</true>
</if>
{installCmd}
</div>
</div>
</div>
<div class={"tab-pane fade"} id={"profile-tab-pane"} role={"tabpanel"} aria-labelledby={"profile-tab"} tabindex={"0"}><div class={"card"}>
<if(package[0].type == "lib" || package[0].type == "compile_tool")>
<true>
<div class={"tab-pane fade"} id={"profile-tab-pane"} role={"tabpanel"} aria-labelledby={"profile-tab"} tabindex={"0"}><div class={"card"}>
<div class={"card-body"}>
{Json.Encode({name=name,version = package[0].version })}
</div>
</div>
</div>
</true>
</if>
</div>
<br>
<h3>More options and info</h3>
@@ -77,7 +109,7 @@ func Pages.Package(ctx,name)
<li>Repo: <a href={package[0].repo}>{package[0].repo}</a></li>
</true>
</if>
<li>Last updated: {package[0].uploadDate}</li>
<li>Last updated: {new DateTime(package[0].uploadTime).ToString("%Y/%m/%d %H:%M:%S UTC")}</li>
<li>Latest version: {package[0].version}</li>
<li>Type: {package[0].type}</li>
<li><a href={package[0].download}>Download</a></li>
@@ -118,7 +150,7 @@ func Pages.Package(ctx,name)
<li>Repo: <a href={package[i].repo}>{package[i].repo}</a></li>
</true>
</if>
<li>Uploaded: {package[i].uploadDate}</li>
<li>Uploaded: {new DateTime(package[i].uploadTime).ToString("%Y/%m/%d %H:%M:%S UTC")}</li>
<li>Type: {package[i].type}</li>
<li><a href={package[i].download}>Download</a></li>
</ul>

View File

@@ -1,6 +1,8 @@
func Pages.Packages(ctx)
{
var q = ctx.QueryParams.TryGetFirst("q");
var page = ParseLong(ctx.QueryParams.TryGetFirst("page"));
page = TypeOf(page) != "Long" ? 1 : page;
if(TypeOf(q) != "String") q = "";
var pages = [
{
@@ -34,8 +36,8 @@ func Pages.Packages(ctx)
</article>
+
<ul>
<each(var item : DB.QueryPackages(q,0,20))>
<raw(Components.Package(item.packageName,item.version,item.accountName,item.uploadDate,item.description))>
<each(var item : DB.QueryPackages(q,(page-1)*20,20))>
<raw(Components.Package(item.packageName,item.version,item.accountName,new DateTime(item.uploadTime).ToString("%Y/%m/%d"),item.description))>
</each>
</ul>;
return Shell("Packages",pages,html);

View File

@@ -1,18 +1,15 @@
var count = 0;
func main(args)
{
Console.WriteLine("In main");
var dir = ".";
if(args.Length > 1)
{
dir = args[1];
}
DB.Init(dir);
//should be a route but its crosslang so we can use mountable
mountable.Mount("/package_icon.png", (ctx)=>{
ctx.ResponseHeaders.SetValue("Content-Type", "image/png");
@@ -22,7 +19,7 @@ func main(args)
/*
PUT /api/v1/upload Authorization Bearer
POST /api/v1/login Json object with username and password returns json object with either 200 for success {"token": "TOKEN_VAL"} or non 2XX if fails {"reason": "SOME ERROR"}
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-prod"} 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
@@ -149,7 +146,7 @@ func main(args)
var csrf = ctx.QueryParams.TryGetFirst("csrf");
var result = { Success=false, Reason = "Invalid CSRF"};
if(DB.VerifyCSRF(session,csrf))
if(!DB.VerifyCSRF(session,csrf))
{
var userId = DB.GetUserIdFromSession(session);
result = DB.UploadPackage(userId, filePath);
@@ -175,6 +172,100 @@ func main(args)
}
}
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/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);
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)
});
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");