From e367ed68084178d7c372e87ddc610d1774e82dc3 Mon Sep 17 00:00:00 2001
From: spiral <spiral@spiral.sh>
Date: Thu, 14 Oct 2021 09:35:20 -0400
Subject: [PATCH] feat(apiv2): post/patch endpoints

---
 .../Controllers/v2/GroupControllerV2.cs       | 47 ++++++++++++++-----
 .../Controllers/v2/MemberControllerV2.cs      | 45 ++++++++++++++----
 .../Controllers/v2/SwitchControllerV2.cs      |  3 +-
 .../Controllers/v2/SystemControllerV2.cs      | 17 ++++---
 4 files changed, 83 insertions(+), 29 deletions(-)

diff --git a/PluralKit.API/Controllers/v2/GroupControllerV2.cs b/PluralKit.API/Controllers/v2/GroupControllerV2.cs
index 3604ceaa..4b351e67 100644
--- a/PluralKit.API/Controllers/v2/GroupControllerV2.cs
+++ b/PluralKit.API/Controllers/v2/GroupControllerV2.cs
@@ -37,12 +37,26 @@ namespace PluralKit.API
         }
 
         [HttpPost("groups")]
-        public async Task<IActionResult> GroupCreate(string group_id)
+        public async Task<IActionResult> GroupCreate([FromBody] JObject data)
         {
-            return new ObjectResult("Unimplemented")
-            {
-                StatusCode = 501
-            };
+            var system = await ResolveSystem("@me");
+
+            var patch = GroupPatch.FromJson(data);
+            patch.AssertIsValid();
+            if (!patch.Name.IsPresent)
+                patch.Errors.Add(new ValidationError("name", $"Key 'name' is required when creating new group."));
+            if (patch.Errors.Count > 0)
+                throw new ModelParseError(patch.Errors);
+
+            using var conn = await _db.Obtain();
+            using var tx = await conn.BeginTransactionAsync();
+
+            var newGroup = await _repo.CreateGroup(system.Id, patch.Name.Value, conn);
+            newGroup = await _repo.UpdateGroup(newGroup.Id, patch, conn);
+
+            await tx.CommitAsync();
+
+            return Ok(newGroup.ToJson(LookupContext.ByOwner));
         }
 
         [HttpGet("groups/{groupRef}")]
@@ -57,13 +71,24 @@ namespace PluralKit.API
             return Ok(group.ToJson(this.ContextFor(group), systemStr: system.Hid));
         }
 
-        [HttpPatch("groups/{group_id}")]
-        public async Task<IActionResult> GroupPatch(string group_id)
+        [HttpPatch("groups/{groupRef}")]
+        public async Task<IActionResult> DoGroupPatch(string groupRef, [FromBody] JObject data)
         {
-            return new ObjectResult("Unimplemented")
-            {
-                StatusCode = 501
-            };
+            var system = await ResolveSystem("@me");
+            var group = await ResolveGroup(groupRef);
+            if (group == null)
+                throw Errors.GroupNotFound;
+            if (group.System != system.Id)
+                throw Errors.NotOwnGroupError;
+
+            var patch = GroupPatch.FromJson(data);
+
+            patch.AssertIsValid();
+            if (patch.Errors.Count > 0)
+                throw new ModelParseError(patch.Errors);
+
+            var newGroup = await _repo.UpdateGroup(group.Id, patch);
+            return Ok(newGroup.ToJson(LookupContext.ByOwner));
         }
 
         [HttpDelete("groups/{groupRef}")]
diff --git a/PluralKit.API/Controllers/v2/MemberControllerV2.cs b/PluralKit.API/Controllers/v2/MemberControllerV2.cs
index 2b72618c..c7b8c090 100644
--- a/PluralKit.API/Controllers/v2/MemberControllerV2.cs
+++ b/PluralKit.API/Controllers/v2/MemberControllerV2.cs
@@ -40,10 +40,24 @@ namespace PluralKit.API
         [HttpPost("members")]
         public async Task<IActionResult> MemberCreate([FromBody] JObject data)
         {
-            return new ObjectResult("Unimplemented")
-            {
-                StatusCode = 501
-            };
+            var patch = MemberPatch.FromJSON(data);
+            patch.AssertIsValid();
+            if (!patch.Name.IsPresent)
+                patch.Errors.Add(new ValidationError("name", $"Key 'name' is required when creating new member."));
+            if (patch.Errors.Count > 0)
+                throw new ModelParseError(patch.Errors);
+
+            var system = await ResolveSystem("@me");
+
+            using var conn = await _db.Obtain();
+            using var tx = await conn.BeginTransactionAsync();
+
+            var newMember = await _repo.CreateMember(system.Id, patch.Name.Value, conn);
+            newMember = await _repo.UpdateMember(newMember.Id, patch, conn);
+
+            await tx.CommitAsync();
+
+            return Ok(newMember.ToJson(LookupContext.ByOwner, v: APIVersion.V2));
         }
 
         [HttpGet("members/{memberRef}")]
@@ -58,13 +72,24 @@ namespace PluralKit.API
             return Ok(member.ToJson(this.ContextFor(member), systemStr: system.Hid, v: APIVersion.V2));
         }
 
-        [HttpPatch("members/{member}")]
-        public async Task<IActionResult> MemberPatch(string member, [FromBody] JObject data)
+        [HttpPatch("members/{memberRef}")]
+        public async Task<IActionResult> DoMemberPatch(string memberRef, [FromBody] JObject data)
         {
-            return new ObjectResult("Unimplemented")
-            {
-                StatusCode = 501
-            };
+            var system = await ResolveSystem("@me");
+            var member = await ResolveMember(memberRef);
+            if (member == null)
+                throw Errors.MemberNotFound;
+            if (member.System != system.Id)
+                throw Errors.NotOwnMemberError;
+
+            var patch = MemberPatch.FromJSON(data, APIVersion.V2);
+
+            patch.AssertIsValid();
+            if (patch.Errors.Count > 0)
+                throw new ModelParseError(patch.Errors);
+
+            var newMember = await _repo.UpdateMember(member.Id, patch);
+            return Ok(newMember.ToJson(LookupContext.ByOwner, v: APIVersion.V2));
         }
 
         [HttpDelete("members/{memberRef}")]
diff --git a/PluralKit.API/Controllers/v2/SwitchControllerV2.cs b/PluralKit.API/Controllers/v2/SwitchControllerV2.cs
index 14f908fe..d291991c 100644
--- a/PluralKit.API/Controllers/v2/SwitchControllerV2.cs
+++ b/PluralKit.API/Controllers/v2/SwitchControllerV2.cs
@@ -165,8 +165,7 @@ namespace PluralKit.API
 
             var valueStr = data.Value<string>("timestamp").NullIfEmpty();
             if (valueStr == null)
-                // todo
-                throw Errors.GenericBadRequest;
+                throw new ModelParseError(new List<ValidationError>() { new ValidationError("timestamp", $"Key 'timestamp' is required.") });
 
             var value = Instant.FromDateTimeOffset(DateTime.Parse(valueStr).ToUniversalTime());
 
diff --git a/PluralKit.API/Controllers/v2/SystemControllerV2.cs b/PluralKit.API/Controllers/v2/SystemControllerV2.cs
index b7432166..4cbcc1c0 100644
--- a/PluralKit.API/Controllers/v2/SystemControllerV2.cs
+++ b/PluralKit.API/Controllers/v2/SystemControllerV2.cs
@@ -24,13 +24,18 @@ namespace PluralKit.API
             else return Ok(system.ToJson(this.ContextFor(system), v: APIVersion.V2));
         }
 
-        [HttpPatch("{system}")]
-        public async Task<IActionResult> SystemPatch(string system, [FromBody] JObject data)
+        [HttpPatch]
+        public async Task<IActionResult> DoSystemPatch([FromBody] JObject data)
         {
-            return new ObjectResult("Unimplemented")
-            {
-                StatusCode = 501
-            };
+            var system = await ResolveSystem("@me");
+            var patch = SystemPatch.FromJSON(data, APIVersion.V2);
+
+            patch.AssertIsValid();
+            if (patch.Errors.Count > 0)
+                throw new ModelParseError(patch.Errors);
+
+            var newSystem = await _repo.UpdateSystem(system.Id, patch);
+            return Ok(newSystem.ToJson(LookupContext.ByOwner, v: APIVersion.V2));
         }
     }
 }
\ No newline at end of file