Skip to content

Commit

Permalink
Merge branch 'main' into dfx-pocket-ic
Browse files Browse the repository at this point in the history
  • Loading branch information
ZenVoich committed Dec 10, 2024
2 parents a998e6a + b7df34a commit aa1e9dd
Show file tree
Hide file tree
Showing 36 changed files with 1,059 additions and 343 deletions.
27 changes: 13 additions & 14 deletions backend/main/PackagePublisher.mo
Original file line number Diff line number Diff line change
Expand Up @@ -66,20 +66,19 @@ module {
};
};

let isNewPackage = registry.getHighestVersion(config.name) == null;

// check permissions
switch (registry.getPackageOwner(config.name)) {
case (null) {
// deny '.' and '_' in name for new packages
for (char in config.name.chars()) {
let err = #err("invalid config: unexpected char '" # Char.toText(char) # "' in name '" # config.name # "'");
if (char == '.' or char == '_') {
return err;
};
};
};
case (?owner) {
if (owner != caller) {
return #err("You don't have permissions to publish this package");
if (not isNewPackage and not registry.isOwner(config.name, caller) and not registry.isMaintainer(config.name, caller)) {
return #err("Only owners and maintainers can publish packages");
};

// deny '.' and '_' in name for new packages
if (isNewPackage) {
for (char in config.name.chars()) {
let err = #err("invalid config: unexpected char '" # Char.toText(char) # "' in name '" # config.name # "'");
if (char == '.' or char == '_') {
return err;
};
};
};
Expand All @@ -103,7 +102,7 @@ module {
if (dep.repo.size() == 0 and registry.getPackageConfig(PackageUtils.getDepName(dep.name), dep.version) == null) {
return #err("Dependency " # packageId # " not found in registry");
};
if (dep.repo.size() != 0 and registry.getPackageOwner(PackageUtils.getDepName(dep.name)) != null) {
if (dep.repo.size() != 0 and registry.getHighestVersion(PackageUtils.getDepName(dep.name)) != null) {
return #err("GitHub dependency `" # dep.name # "` conflicts with an existing package in the Mops registry. Please change the dependency name in mops.toml file.");
};
};
Expand Down
101 changes: 75 additions & 26 deletions backend/main/main-canister.mo
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@ actor class Main() {
let API_VERSION = "1.3"; // (!) make changes in pair with cli

var packageVersions = TrieMap.TrieMap<PackageName, [PackageVersion]>(Text.equal, Text.hash);
var packageOwners = TrieMap.TrieMap<PackageName, Principal>(Text.equal, Text.hash);
var packageOwners = TrieMap.TrieMap<PackageName, Principal>(Text.equal, Text.hash); // legacy
var ownersByPackage = TrieMap.TrieMap<PackageName, [Principal]>(Text.equal, Text.hash);
var maintainersByPackage = TrieMap.TrieMap<PackageName, [Principal]>(Text.equal, Text.hash);
var highestConfigs = TrieMap.TrieMap<PackageName, PackageConfigV3>(Text.equal, Text.hash);

var packageConfigs = TrieMap.TrieMap<PackageId, PackageConfigV3>(Text.equal, Text.hash);
Expand All @@ -76,7 +78,8 @@ actor class Main() {

var registry = Registry.Registry(
packageVersions,
packageOwners,
ownersByPackage,
maintainersByPackage,
highestConfigs,
packageConfigs,
packagePublications,
Expand Down Expand Up @@ -489,8 +492,28 @@ actor class Main() {
users.setUserProp(caller, prop, value);
};

public shared ({caller}) func transferOwnership(packageName : PackageName, newOwner : Principal) : async Result.Result<(), Text> {
registry.transferOwnership(caller, packageName, newOwner);
public query func getPackageOwners(packageName : PackageName) : async [Principal] {
registry.getPackageOwners(packageName);
};

public query func getPackageMaintainers(packageName : PackageName) : async [Principal] {
registry.getPackageMaintainers(packageName);
};

public shared ({caller}) func addOwner(packageName : PackageName, newOwner : Principal) : async Result.Result<(), Text> {
registry.addOwner(caller, packageName, newOwner);
};

public shared ({caller}) func addMaintainer(packageName : PackageName, newMaintainer : Principal) : async Result.Result<(), Text> {
registry.addMaintainer(caller, packageName, newMaintainer);
};

public shared ({caller}) func removeOwner(packageName : PackageName, owner : Principal) : async Result.Result<(), Text> {
registry.removeOwner(caller, packageName, owner);
};

public shared ({caller}) func removeMaintainer(packageName : PackageName, maintainer : Principal) : async Result.Result<(), Text> {
registry.removeMaintainer(caller, packageName, maintainer);
};

// BADGES
Expand Down Expand Up @@ -526,10 +549,11 @@ actor class Main() {
let backupManager = Backup.BackupManager(backupStateV2, {maxBackups = 20});

type BackupChunk = {
#v7 : {
#v8 : {
#packagePublications : [(PackageId, PackagePublication)];
#packageVersions : [(PackageName, [PackageVersion])];
#packageOwners : [(PackageName, Principal)];
#ownersByPackage : [(PackageName, [Principal])];
#maintainersByPackage : [(PackageName, [Principal])];
#packageConfigs : [(PackageId, PackageConfigV3)];
#highestConfigs : [(PackageName, PackageConfigV3)];
#fileIdsByPackage : [(PackageId, [FileId])];
Expand All @@ -555,22 +579,23 @@ actor class Main() {
};

func _backup() : async () {
let backup = backupManager.NewBackup("v7");
let backup = backupManager.NewBackup("v8");
await backup.startBackup();
await backup.uploadChunk(to_candid(#v7(#packagePublications(Iter.toArray(packagePublications.entries()))) : BackupChunk));
await backup.uploadChunk(to_candid(#v7(#packageVersions(Iter.toArray(packageVersions.entries()))) : BackupChunk));
await backup.uploadChunk(to_candid(#v7(#packageOwners(Iter.toArray(packageOwners.entries()))) : BackupChunk));
await backup.uploadChunk(to_candid(#v7(#fileIdsByPackage(Iter.toArray(fileIdsByPackage.entries()))) : BackupChunk));
await backup.uploadChunk(to_candid(#v7(#hashByFileId(Iter.toArray(hashByFileId.entries()))) : BackupChunk));
await backup.uploadChunk(to_candid(#v7(#packageFileStats(Iter.toArray(packageFileStats.entries()))) : BackupChunk));
await backup.uploadChunk(to_candid(#v7(#packageTestStats(Iter.toArray(packageTestStats.entries()))) : BackupChunk));
await backup.uploadChunk(to_candid(#v7(#packageBenchmarks(Iter.toArray(packageBenchmarks.entries()))) : BackupChunk));
await backup.uploadChunk(to_candid(#v7(#packageNotes(Iter.toArray(packageNotes.entries()))) : BackupChunk));
await backup.uploadChunk(to_candid(#v7(#downloadLog(downloadLog.toStable())) : BackupChunk));
await backup.uploadChunk(to_candid(#v7(#storageManager(storageManager.toStable())) : BackupChunk));
await backup.uploadChunk(to_candid(#v7(#users(users.toStable())) : BackupChunk));
await backup.uploadChunk(to_candid(#v7(#highestConfigs(Iter.toArray(highestConfigs.entries()))) : BackupChunk));
await backup.uploadChunk(to_candid(#v7(#packageConfigs(Iter.toArray(packageConfigs.entries()))) : BackupChunk));
await backup.uploadChunk(to_candid(#v8(#packagePublications(Iter.toArray(packagePublications.entries()))) : BackupChunk));
await backup.uploadChunk(to_candid(#v8(#packageVersions(Iter.toArray(packageVersions.entries()))) : BackupChunk));
await backup.uploadChunk(to_candid(#v8(#ownersByPackage(Iter.toArray(ownersByPackage.entries()))) : BackupChunk));
await backup.uploadChunk(to_candid(#v8(#maintainersByPackage(Iter.toArray(maintainersByPackage.entries()))) : BackupChunk));
await backup.uploadChunk(to_candid(#v8(#fileIdsByPackage(Iter.toArray(fileIdsByPackage.entries()))) : BackupChunk));
await backup.uploadChunk(to_candid(#v8(#hashByFileId(Iter.toArray(hashByFileId.entries()))) : BackupChunk));
await backup.uploadChunk(to_candid(#v8(#packageFileStats(Iter.toArray(packageFileStats.entries()))) : BackupChunk));
await backup.uploadChunk(to_candid(#v8(#packageTestStats(Iter.toArray(packageTestStats.entries()))) : BackupChunk));
await backup.uploadChunk(to_candid(#v8(#packageBenchmarks(Iter.toArray(packageBenchmarks.entries()))) : BackupChunk));
await backup.uploadChunk(to_candid(#v8(#packageNotes(Iter.toArray(packageNotes.entries()))) : BackupChunk));
await backup.uploadChunk(to_candid(#v8(#downloadLog(downloadLog.toStable())) : BackupChunk));
await backup.uploadChunk(to_candid(#v8(#storageManager(storageManager.toStable())) : BackupChunk));
await backup.uploadChunk(to_candid(#v8(#users(users.toStable())) : BackupChunk));
await backup.uploadChunk(to_candid(#v8(#highestConfigs(Iter.toArray(highestConfigs.entries()))) : BackupChunk));
await backup.uploadChunk(to_candid(#v8(#packageConfigs(Iter.toArray(packageConfigs.entries()))) : BackupChunk));
await backup.finishBackup();
};

Expand All @@ -580,7 +605,7 @@ actor class Main() {
assert(Utils.isAdmin(caller));

await backupManager.restore(backupId, func(blob : Blob) {
let ?#v7(chunk) : ?BackupChunk = from_candid(blob) else Debug.trap("Failed to restore chunk");
let ?#v8(chunk) : ?BackupChunk = from_candid(blob) else Debug.trap("Failed to restore chunk");

switch (chunk) {
case (#packagePublications(packagePublicationsStable)) {
Expand All @@ -589,8 +614,11 @@ actor class Main() {
case (#packageVersions(packageVersionsStable)) {
packageVersions := TrieMap.fromEntries<PackageName, [PackageVersion]>(packageVersionsStable.vals(), Text.equal, Text.hash);
};
case (#packageOwners(packageOwnersStable)) {
packageOwners := TrieMap.fromEntries<PackageName, Principal>(packageOwnersStable.vals(), Text.equal, Text.hash);
case (#ownersByPackage(ownersByPackageStable)) {
ownersByPackage := TrieMap.fromEntries<PackageName, [Principal]>(ownersByPackageStable.vals(), Text.equal, Text.hash);
};
case (#maintainersByPackage(maintainersByPackageStable)) {
maintainersByPackage := TrieMap.fromEntries<PackageName, [Principal]>(maintainersByPackageStable.vals(), Text.equal, Text.hash);
};
case (#fileIdsByPackage(fileIdsByPackageStable)) {
fileIdsByPackage := TrieMap.fromEntries<PackageId, [FileId]>(fileIdsByPackageStable.vals(), Text.equal, Text.hash);
Expand Down Expand Up @@ -634,7 +662,8 @@ actor class Main() {
// re-init registry
registry := Registry.Registry(
packageVersions,
packageOwners,
ownersByPackage,
maintainersByPackage,
highestConfigs,
packageConfigs,
packagePublications,
Expand All @@ -652,6 +681,8 @@ actor class Main() {
stable var packagePublicationsStable : [(PackageId, PackagePublication)] = [];
stable var packageVersionsStable : [(PackageName, [PackageVersion])] = [];
stable var packageOwnersStable : [(PackageName, Principal)] = [];
stable var ownersByPackageStable : [(PackageName, [Principal])] = [];
stable var maintainersByPackageStable : [(PackageName, [Principal])] = [];

stable var packageConfigsStableV3 : [(PackageId, PackageConfigV3)] = [];
stable var highestConfigsStableV3 : [(PackageName, PackageConfigV3)] = [];
Expand All @@ -671,6 +702,8 @@ actor class Main() {
packagePublicationsStable := Iter.toArray(packagePublications.entries());
packageVersionsStable := Iter.toArray(packageVersions.entries());
packageOwnersStable := Iter.toArray(packageOwners.entries());
ownersByPackageStable := Iter.toArray(ownersByPackage.entries());
maintainersByPackageStable := Iter.toArray(maintainersByPackage.entries());
fileIdsByPackageStable := Iter.toArray(fileIdsByPackage.entries());
hashByFileIdStable := Iter.toArray(hashByFileId.entries());
packageFileStatsStable := Iter.toArray(packageFileStats.entries());
Expand Down Expand Up @@ -698,6 +731,21 @@ actor class Main() {
packageVersions := TrieMap.fromEntries<PackageName, [PackageVersion]>(packageVersionsStable.vals(), Text.equal, Text.hash);
packageVersionsStable := [];

// migrate packageOwners -> ownersByPackage
if (ownersByPackageStable.size() == 0) {
ownersByPackage :=
packageOwnersStable.vals()
|> Iter.map<(PackageName, Principal), (PackageName, [Principal])>(_, func((name, owner)) = (name, [owner]))
|> TrieMap.fromEntries<PackageName, [Principal]>(_, Text.equal, Text.hash);
}
else {
ownersByPackage := TrieMap.fromEntries<PackageName, [Principal]>(ownersByPackageStable.vals(), Text.equal, Text.hash);
ownersByPackageStable := [];
};

maintainersByPackage := TrieMap.fromEntries<PackageName, [Principal]>(maintainersByPackageStable.vals(), Text.equal, Text.hash);
maintainersByPackageStable := [];

packageOwners := TrieMap.fromEntries<PackageName, Principal>(packageOwnersStable.vals(), Text.equal, Text.hash);
packageOwnersStable := [];

Expand Down Expand Up @@ -732,7 +780,8 @@ actor class Main() {

registry := Registry.Registry(
packageVersions,
packageOwners,
ownersByPackage,
maintainersByPackage,
highestConfigs,
packageConfigs,
packagePublications,
Expand Down
111 changes: 98 additions & 13 deletions backend/main/registry/Registry.mo
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Option "mo:base/Option";
import Array "mo:base/Array";
import Time "mo:base/Time";
import Result "mo:base/Result";
import Principal "mo:base/Principal";

import Types "../types";
import Semver "../utils/semver";
Expand Down Expand Up @@ -34,7 +35,8 @@ module {

public class Registry(
packageVersions : TrieMap.TrieMap<PackageName, [PackageVersion]>,
packageOwners : TrieMap.TrieMap<PackageName, Principal>,
ownersByPackage : TrieMap.TrieMap<PackageName, [Principal]>,
maintainersByPackage : TrieMap.TrieMap<PackageName, [Principal]>,
highestConfigs : TrieMap.TrieMap<PackageName, PackageConfigV3>,
packageConfigs : TrieMap.TrieMap<PackageId, PackageConfigV3>,
packagePublications : TrieMap.TrieMap<PackageId, PackagePublication>,
Expand All @@ -59,7 +61,13 @@ module {
packageVersions.put(newRelease.config.name, Array.append(versions, [newRelease.config.version]));

packageConfigs.put(packageId, newRelease.config);
packageOwners.put(newRelease.config.name, newRelease.userId);

// add owner for new package
let owners = getPackageOwners(newRelease.config.name);
if (owners.size() == 0) {
ownersByPackage.put(newRelease.config.name, [newRelease.userId]);
};

packagePublications.put(packageId, {
user = newRelease.userId;
time = Time.now();
Expand Down Expand Up @@ -119,10 +127,6 @@ module {
// By package name
// -----------------------------

public func getPackageOwner(name : PackageName) : ?Principal {
packageOwners.get(name);
};

public func getPackageVersions(name : PackageName) : ?[PackageVersion] {
packageVersions.get(name);
};
Expand Down Expand Up @@ -169,17 +173,98 @@ module {
// Package ownership
// -----------------------------

public func transferOwnership(caller : Principal, packageName : PackageName, newOwner : Principal) : Result.Result<(), Text> {
let ?oldOwner = packageOwners.get(packageName) else return #err("Package not found");
public func getPackageOwners(name : PackageName) : [Principal] {
Option.get(ownersByPackage.get(name), []);
};

public func getPackageMaintainers(name : PackageName) : [Principal] {
Option.get(maintainersByPackage.get(name), []);
};

public func isOwner(name : PackageName, principal : Principal) : Bool {
for (owner in getPackageOwners(name).vals()) {
if (owner == principal) {
return true;
};
};
return false;
};

public func isMaintainer(name : PackageName, principal : Principal) : Bool {
for (maintainer in getPackageMaintainers(name).vals()) {
if (maintainer == principal) {
return true;
};
};
return false;
};

public func addOwner(caller : Principal, packageName : PackageName, newOwner : Principal) : Result.Result<(), Text> {
let ?owners = ownersByPackage.get(packageName) else return #err("Package not found");

if (isOwner(packageName, newOwner)) {
return #err("User is already an owner");
};
if (not isOwner(packageName, caller)) {
return #err("Only owners can add owners");
};
if (owners.size() >= 5) {
return #err("Maximum number of owners reached");
};

ownersByPackage.put(packageName, Array.append(owners, [newOwner]));
#ok;
};

public func addMaintainer(caller : Principal, packageName : PackageName, newMaintainer : Principal) : Result.Result<(), Text> {
let maintainers = Option.get(maintainersByPackage.get(packageName), []);

if (isMaintainer(packageName, newMaintainer)) {
return #err("User is already a maintainer");
};
if (not isOwner(packageName, caller)) {
return #err("Only owners can add maintainers");
};
if (maintainers.size() >= 5) {
return #err("Maximum number of maintainers reached");
};

maintainersByPackage.put(packageName, Array.append(maintainers, [newMaintainer]));
#ok;
};

public func removeOwner(caller : Principal, packageName : PackageName, ownerToRemove : Principal) : Result.Result<(), Text> {
let ?owners = ownersByPackage.get(packageName) else return #err("Package not found");

if (not isOwner(packageName, ownerToRemove)) {
return #err("User is not an owner");
};
if (not isOwner(packageName, caller)) {
return #err("Only owners can remove owners");
};
if (owners.size() <= 1) {
return #err("At least one owner is required");
};

ownersByPackage.put(packageName, Array.filter(owners, func(owner : Principal) : Bool {
owner != ownerToRemove;
}));
#ok;
};

public func removeMaintainer(caller : Principal, packageName : PackageName, maintainerToRemove : Principal) : Result.Result<(), Text> {
let maintainers = Option.get(maintainersByPackage.get(packageName), []);

if (oldOwner != caller) {
return #err("Only owner can transfer ownership");
if (not isMaintainer(packageName, maintainerToRemove)) {
return #err("User is not a maintainer");
};
if (newOwner == caller) {
return #err("You can't transfer ownership to yourself");
if (not isOwner(packageName, caller)) {
return #err("Only owners can remove maintainers");
};

packageOwners.put(packageName, newOwner);
maintainersByPackage.put(packageName, Array.filter(maintainers, func(maintainer : Principal) : Bool {
maintainer != maintainerToRemove;
}));
#ok;
};
};
Expand Down
Loading

0 comments on commit aa1e9dd

Please sign in to comment.