diff --git a/Meadowlark-js/backends/meadowlark-elasticsearch-backend/package.json b/Meadowlark-js/backends/meadowlark-elasticsearch-backend/package.json index 45b75233..58d23e62 100644 --- a/Meadowlark-js/backends/meadowlark-elasticsearch-backend/package.json +++ b/Meadowlark-js/backends/meadowlark-elasticsearch-backend/package.json @@ -1,7 +1,7 @@ { "name": "@edfi/meadowlark-elasticsearch-backend", "main": "dist/index.js", - "version": "0.4.1-pre.5", + "version": "0.4.1-pre.6", "description": "Meadowlark backend plugin for elasticsearch", "license": "Apache-2.0", "publishConfig": { @@ -19,8 +19,8 @@ "build:copy-non-ts": "copyfiles -u 1 -e \"**/*.ts\" \"src/**/*\" dist --verbose" }, "dependencies": { - "@edfi/meadowlark-core": "0.4.1-pre.5", - "@edfi/meadowlark-utilities": "0.4.1-pre.5", + "@edfi/meadowlark-core": "0.4.1-pre.6", + "@edfi/meadowlark-utilities": "0.4.1-pre.6", "@elastic/elasticsearch": "^8.10.0", "@elastic/transport": "^8.3.4" }, diff --git a/Meadowlark-js/backends/meadowlark-mongodb-backend/package.json b/Meadowlark-js/backends/meadowlark-mongodb-backend/package.json index 4dabba8a..24f0ca67 100644 --- a/Meadowlark-js/backends/meadowlark-mongodb-backend/package.json +++ b/Meadowlark-js/backends/meadowlark-mongodb-backend/package.json @@ -1,7 +1,7 @@ { "name": "@edfi/meadowlark-mongodb-backend", "main": "dist/index.js", - "version": "0.4.1-pre.5", + "version": "0.4.1-pre.6", "description": "Meadowlark backend plugin for MongoDB", "license": "Apache-2.0", "publishConfig": { @@ -19,9 +19,9 @@ "build:copy-non-ts": "copyfiles -u 1 -e \"**/*.ts\" \"src/**/*\" dist --verbose" }, "dependencies": { - "@edfi/meadowlark-authz-server": "0.4.1-pre.5", - "@edfi/meadowlark-core": "0.4.1-pre.5", - "@edfi/meadowlark-utilities": "0.4.1-pre.5", + "@edfi/meadowlark-authz-server": "0.4.1-pre.6", + "@edfi/meadowlark-core": "0.4.1-pre.6", + "@edfi/meadowlark-utilities": "0.4.1-pre.6", "async-retry": "^1.3.3", "mongodb": "^5.9.2", "ramda": "0.29.1" diff --git a/Meadowlark-js/backends/meadowlark-opensearch-backend/package.json b/Meadowlark-js/backends/meadowlark-opensearch-backend/package.json index b9cae38d..610ac625 100644 --- a/Meadowlark-js/backends/meadowlark-opensearch-backend/package.json +++ b/Meadowlark-js/backends/meadowlark-opensearch-backend/package.json @@ -1,7 +1,7 @@ { "name": "@edfi/meadowlark-opensearch-backend", "main": "dist/index.js", - "version": "0.4.1-pre.5", + "version": "0.4.1-pre.6", "description": "Meadowlark backend plugin for OpenSearch", "license": "Apache-2.0", "publishConfig": { @@ -19,8 +19,8 @@ "build:copy-non-ts": "copyfiles -u 1 -e \"**/*.ts\" \"src/**/*\" dist --verbose" }, "dependencies": { - "@edfi/meadowlark-core": "0.4.1-pre.5", - "@edfi/meadowlark-utilities": "0.4.1-pre.5", + "@edfi/meadowlark-core": "0.4.1-pre.6", + "@edfi/meadowlark-utilities": "0.4.1-pre.6", "@opensearch-project/opensearch": "^2.4.0" }, "devDependencies": { diff --git a/Meadowlark-js/backends/meadowlark-postgresql-backend/package.json b/Meadowlark-js/backends/meadowlark-postgresql-backend/package.json index 287b3a27..405c39ab 100644 --- a/Meadowlark-js/backends/meadowlark-postgresql-backend/package.json +++ b/Meadowlark-js/backends/meadowlark-postgresql-backend/package.json @@ -1,7 +1,7 @@ { "name": "@edfi/meadowlark-postgresql-backend", "main": "dist/index.js", - "version": "0.4.1-pre.5", + "version": "0.4.1-pre.6", "description": "Meadowlark backend plugin for PostgreSQL", "license": "Apache-2.0", "publishConfig": { @@ -19,9 +19,9 @@ "build:copy-non-ts": "copyfiles -u 1 -e \"**/*.ts\" \"src/**/*\" dist --verbose" }, "dependencies": { - "@edfi/meadowlark-authz-server": "0.4.1-pre.5", - "@edfi/meadowlark-core": "0.4.1-pre.5", - "@edfi/meadowlark-utilities": "0.4.1-pre.5", + "@edfi/meadowlark-authz-server": "0.4.1-pre.6", + "@edfi/meadowlark-core": "0.4.1-pre.6", + "@edfi/meadowlark-utilities": "0.4.1-pre.6", "pg": "^8.11.3", "pg-format": "^1.0.4", "ramda": "0.29.1" diff --git a/Meadowlark-js/lerna.json b/Meadowlark-js/lerna.json index 86c17789..5eb49044 100644 --- a/Meadowlark-js/lerna.json +++ b/Meadowlark-js/lerna.json @@ -1,5 +1,5 @@ { "lerna": "8.0.2", - "version": "0.4.1-pre.5", + "version": "0.4.1-pre.6", "npmClient": "npm" } diff --git a/Meadowlark-js/package-lock.json b/Meadowlark-js/package-lock.json index 1ce242d1..35bf360b 100644 --- a/Meadowlark-js/package-lock.json +++ b/Meadowlark-js/package-lock.json @@ -54,11 +54,11 @@ }, "backends/meadowlark-elasticsearch-backend": { "name": "@edfi/meadowlark-elasticsearch-backend", - "version": "0.4.1-pre.5", + "version": "0.4.1-pre.6", "license": "Apache-2.0", "dependencies": { - "@edfi/meadowlark-core": "0.4.1-pre.5", - "@edfi/meadowlark-utilities": "0.4.1-pre.5", + "@edfi/meadowlark-core": "0.4.1-pre.6", + "@edfi/meadowlark-utilities": "0.4.1-pre.6", "@elastic/elasticsearch": "^8.10.0", "@elastic/transport": "^8.3.4" }, @@ -72,12 +72,12 @@ }, "backends/meadowlark-mongodb-backend": { "name": "@edfi/meadowlark-mongodb-backend", - "version": "0.4.1-pre.5", + "version": "0.4.1-pre.6", "license": "Apache-2.0", "dependencies": { - "@edfi/meadowlark-authz-server": "0.4.1-pre.5", - "@edfi/meadowlark-core": "0.4.1-pre.5", - "@edfi/meadowlark-utilities": "0.4.1-pre.5", + "@edfi/meadowlark-authz-server": "0.4.1-pre.6", + "@edfi/meadowlark-core": "0.4.1-pre.6", + "@edfi/meadowlark-utilities": "0.4.1-pre.6", "async-retry": "^1.3.3", "mongodb": "^5.9.2", "ramda": "0.29.1" @@ -90,11 +90,11 @@ }, "backends/meadowlark-opensearch-backend": { "name": "@edfi/meadowlark-opensearch-backend", - "version": "0.4.1-pre.5", + "version": "0.4.1-pre.6", "license": "Apache-2.0", "dependencies": { - "@edfi/meadowlark-core": "0.4.1-pre.5", - "@edfi/meadowlark-utilities": "0.4.1-pre.5", + "@edfi/meadowlark-core": "0.4.1-pre.6", + "@edfi/meadowlark-utilities": "0.4.1-pre.6", "@opensearch-project/opensearch": "^2.4.0" }, "devDependencies": { @@ -107,12 +107,12 @@ }, "backends/meadowlark-postgresql-backend": { "name": "@edfi/meadowlark-postgresql-backend", - "version": "0.4.1-pre.5", + "version": "0.4.1-pre.6", "license": "Apache-2.0", "dependencies": { - "@edfi/meadowlark-authz-server": "0.4.1-pre.5", - "@edfi/meadowlark-core": "0.4.1-pre.5", - "@edfi/meadowlark-utilities": "0.4.1-pre.5", + "@edfi/meadowlark-authz-server": "0.4.1-pre.6", + "@edfi/meadowlark-core": "0.4.1-pre.6", + "@edfi/meadowlark-utilities": "0.4.1-pre.6", "pg": "^8.11.3", "pg-format": "^1.0.4", "ramda": "0.29.1" @@ -17509,11 +17509,11 @@ }, "packages/meadowlark-authz-server": { "name": "@edfi/meadowlark-authz-server", - "version": "0.4.1-pre.5", + "version": "0.4.1-pre.6", "license": "Apache-2.0", "dependencies": { "@apideck/better-ajv-errors": "^0.3.6", - "@edfi/meadowlark-utilities": "0.4.1-pre.5", + "@edfi/meadowlark-utilities": "0.4.1-pre.6", "ajv": "^8.12.0", "didyoumean2": "^6.0.1", "dotenv": "^16.3.1", @@ -17563,11 +17563,11 @@ }, "packages/meadowlark-core": { "name": "@edfi/meadowlark-core", - "version": "0.4.1-pre.5", + "version": "0.4.1-pre.6", "license": "Apache-2.0", "dependencies": { "@apideck/better-ajv-errors": "^0.3.6", - "@edfi/meadowlark-utilities": "0.4.1-pre.5", + "@edfi/meadowlark-utilities": "0.4.1-pre.6", "@isaacs/ttlcache": "^1.4.1", "ajv": "^8.12.0", "ajv-formats": "^2.1.1", @@ -17657,7 +17657,7 @@ }, "packages/meadowlark-utilities": { "name": "@edfi/meadowlark-utilities", - "version": "0.4.1-pre.5", + "version": "0.4.1-pre.6", "license": "Apache-2.0", "dependencies": { "pino": "^8.15.7", @@ -17704,12 +17704,12 @@ }, "services/meadowlark-fastify": { "name": "@edfi/meadowlark-fastify", - "version": "0.4.1-pre.5", + "version": "0.4.1-pre.6", "license": "Apache-2.0", "dependencies": { - "@edfi/meadowlark-authz-server": "0.4.1-pre.5", - "@edfi/meadowlark-core": "0.4.1-pre.5", - "@edfi/meadowlark-utilities": "0.4.1-pre.5", + "@edfi/meadowlark-authz-server": "0.4.1-pre.6", + "@edfi/meadowlark-core": "0.4.1-pre.6", + "@edfi/meadowlark-utilities": "0.4.1-pre.6", "@fastify/rate-limit": "^6.0.1", "dotenv": "^16.3.1", "fastify": "^3.29.5" @@ -17722,10 +17722,10 @@ }, "tests/e2e": { "name": "@edfi/meadowlark-e2e-tests", - "version": "0.4.1-pre.5", + "version": "0.4.1-pre.6", "license": "Apache-2.0", "devDependencies": { - "@edfi/meadowlark-utilities": "0.4.1-pre.5", + "@edfi/meadowlark-utilities": "0.4.1-pre.6", "@testcontainers/mongodb": "^10.3.1", "@testcontainers/postgresql": "^10.3.1", "@types/chance": "^1.1.6", diff --git a/Meadowlark-js/packages/meadowlark-authz-server/package.json b/Meadowlark-js/packages/meadowlark-authz-server/package.json index b81190bb..fbbf3d4d 100644 --- a/Meadowlark-js/packages/meadowlark-authz-server/package.json +++ b/Meadowlark-js/packages/meadowlark-authz-server/package.json @@ -1,7 +1,7 @@ { "name": "@edfi/meadowlark-authz-server", "main": "dist/index.js", - "version": "0.4.1-pre.5", + "version": "0.4.1-pre.6", "description": "Meadowlark authorization server", "license": "Apache-2.0", "publishConfig": { @@ -14,7 +14,7 @@ ], "dependencies": { "@apideck/better-ajv-errors": "^0.3.6", - "@edfi/meadowlark-utilities": "0.4.1-pre.5", + "@edfi/meadowlark-utilities": "0.4.1-pre.6", "ajv": "^8.12.0", "didyoumean2": "^6.0.1", "dotenv": "^16.3.1", diff --git a/Meadowlark-js/packages/meadowlark-core/package.json b/Meadowlark-js/packages/meadowlark-core/package.json index 11b30147..cefab3a4 100644 --- a/Meadowlark-js/packages/meadowlark-core/package.json +++ b/Meadowlark-js/packages/meadowlark-core/package.json @@ -1,7 +1,7 @@ { "name": "@edfi/meadowlark-core", "main": "dist/index.js", - "version": "0.4.1-pre.5", + "version": "0.4.1-pre.6", "description": "Meadowlark core functionality", "license": "Apache-2.0", "publishConfig": { @@ -14,7 +14,7 @@ ], "dependencies": { "@apideck/better-ajv-errors": "^0.3.6", - "@edfi/meadowlark-utilities": "0.4.1-pre.5", + "@edfi/meadowlark-utilities": "0.4.1-pre.6", "@isaacs/ttlcache": "^1.4.1", "ajv": "^8.12.0", "ajv-formats": "^2.1.1", diff --git a/Meadowlark-js/packages/meadowlark-utilities/package.json b/Meadowlark-js/packages/meadowlark-utilities/package.json index 98889530..7d15de98 100644 --- a/Meadowlark-js/packages/meadowlark-utilities/package.json +++ b/Meadowlark-js/packages/meadowlark-utilities/package.json @@ -1,7 +1,7 @@ { "name": "@edfi/meadowlark-utilities", "main": "dist/index.js", - "version": "0.4.1-pre.5", + "version": "0.4.1-pre.6", "description": "Meadowlark shared utilities", "license": "Apache-2.0", "publishConfig": { diff --git a/Meadowlark-js/services/meadowlark-fastify/package.json b/Meadowlark-js/services/meadowlark-fastify/package.json index eee4d4ed..d8ad6d9e 100644 --- a/Meadowlark-js/services/meadowlark-fastify/package.json +++ b/Meadowlark-js/services/meadowlark-fastify/package.json @@ -1,6 +1,6 @@ { "name": "@edfi/meadowlark-fastify", - "version": "0.4.1-pre.5", + "version": "0.4.1-pre.6", "description": "Meadowlark service using Fastify", "license": "Apache-2.0", "publishConfig": { @@ -12,9 +12,9 @@ "/package.json" ], "dependencies": { - "@edfi/meadowlark-authz-server": "0.4.1-pre.5", - "@edfi/meadowlark-core": "0.4.1-pre.5", - "@edfi/meadowlark-utilities": "0.4.1-pre.5", + "@edfi/meadowlark-authz-server": "0.4.1-pre.6", + "@edfi/meadowlark-core": "0.4.1-pre.6", + "@edfi/meadowlark-utilities": "0.4.1-pre.6", "@fastify/rate-limit": "^6.0.1", "dotenv": "^16.3.1", "fastify": "^3.29.5" diff --git a/Meadowlark-js/tests/e2e/package.json b/Meadowlark-js/tests/e2e/package.json index 6b303beb..3d9fcf98 100644 --- a/Meadowlark-js/tests/e2e/package.json +++ b/Meadowlark-js/tests/e2e/package.json @@ -1,13 +1,13 @@ { "name": "@edfi/meadowlark-e2e-tests", "main": "dist/index.js", - "version": "0.4.1-pre.5", + "version": "0.4.1-pre.6", "description": "Meadowlark Ed-Fi API end to end tests", "license": "Apache-2.0", "private": true, "files": [], "devDependencies": { - "@edfi/meadowlark-utilities": "0.4.1-pre.5", + "@edfi/meadowlark-utilities": "0.4.1-pre.6", "@testcontainers/mongodb": "^10.3.1", "@testcontainers/postgresql": "^10.3.1", "@types/chance": "^1.1.6", diff --git a/Meadowlark.net/.gitignore b/Meadowlark.net/.gitignore index 8a30d258..2499b977 100644 --- a/Meadowlark.net/.gitignore +++ b/Meadowlark.net/.gitignore @@ -396,3 +396,6 @@ FodyWeavers.xsd # JetBrains Rider *.sln.iml + +# DotNetEnv +.env diff --git a/Meadowlark.net/Meadowlark.Net.Core/.env.example b/Meadowlark.net/Meadowlark.Net.Core/.env.example new file mode 100644 index 00000000..08da9b1e --- /dev/null +++ b/Meadowlark.net/Meadowlark.Net.Core/.env.example @@ -0,0 +1,4 @@ +#### PostgreSQL backend options + +POSTGRES_USER= +POSTGRES_PASSWORD= diff --git a/Meadowlark.net/Meadowlark.Net.Core/Backend/Model/GetRequest.cs b/Meadowlark.net/Meadowlark.Net.Core/Backend/Model/GetRequest.cs new file mode 100644 index 00000000..7d1f7f54 --- /dev/null +++ b/Meadowlark.net/Meadowlark.Net.Core/Backend/Model/GetRequest.cs @@ -0,0 +1,4 @@ +using Meadowlark.Net.Core.Model; +namespace Meadowlark.Net.Core.Backend.Model; + +public record GetRequest(DocumentUuid DocumentUuid, ResourceInfo ResourceInfo); diff --git a/Meadowlark.net/Meadowlark.Net.Core/Backend/Model/GetResponse.cs b/Meadowlark.net/Meadowlark.Net.Core/Backend/Model/GetResponse.cs new file mode 100644 index 00000000..cb30cdc1 --- /dev/null +++ b/Meadowlark.net/Meadowlark.Net.Core/Backend/Model/GetResponse.cs @@ -0,0 +1,4 @@ +using Meadowlark.Net.Core.Model; +namespace Meadowlark.Net.Core.Backend.Model; + +public record GetResponse(string EdfiDoc); diff --git a/Meadowlark.net/Meadowlark.Net.Core/Backend/Model/InsertRequest.cs b/Meadowlark.net/Meadowlark.Net.Core/Backend/Model/InsertRequest.cs new file mode 100644 index 00000000..3d94b646 --- /dev/null +++ b/Meadowlark.net/Meadowlark.Net.Core/Backend/Model/InsertRequest.cs @@ -0,0 +1,5 @@ +using Meadowlark.Net.Core.Model; +using Newtonsoft.Json.Linq; +namespace Meadowlark.Net.Core.Backend.Model; + +public record InsertRequest(DocumentUuid DocumentUuid, ResourceInfo ResourceInfo, JObject EdfiDoc); diff --git a/Meadowlark.net/Meadowlark.Net.Core/Backend/Postgresql/Db.cs b/Meadowlark.net/Meadowlark.Net.Core/Backend/Postgresql/Db.cs new file mode 100644 index 00000000..2a4621df --- /dev/null +++ b/Meadowlark.net/Meadowlark.Net.Core/Backend/Postgresql/Db.cs @@ -0,0 +1,101 @@ +using Meadowlark.Net.Core.Backend.Model; +using Meadowlark.Net.Core.Model; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Npgsql; +using NpgsqlTypes; + +namespace Meadowlark.Net.Core.Backend.Postgresql; + +public static class Db +{ + // TODO: Get .env loading working + private static readonly string connectionString = "Server=localhost;Port=5432;User Id=postgres;Password=abcdefgh1!;Database=MeadowlarkNet;"; + + // static Db() + // { + // Load .env file with Postgresql username/password + // DotNetEnv.Env.TraversePath().Load(); + // connectionString: $"Server=localhost;Port=5432;User Id={Environment.GetEnvironmentVariable("POSTGRES_USER")};Password={Environment.GetEnvironmentVariable("POSTGRES_PASSWORD")};Database=MeadowlarkNet;"); + // } + + private static async Task TryCreateTable(ResourceInfo resourceInfo) + { + // Note: ProjectName and ResourceName have been validated against the ApiSchema, so SQL injection is not a concern here + string schemaName = resourceInfo.ProjectName.Value.Replace("-", ""); + string tableName = resourceInfo.ResourceName.Value.Replace("-", ""); + + using var con = new NpgsqlConnection(connectionString); + con.Open(); + using var cmd = new NpgsqlCommand(); + cmd.Connection = con; + + cmd.CommandText = $"CREATE SCHEMA IF NOT EXISTS {schemaName}"; + await cmd.ExecuteNonQueryAsync(); + cmd.CommandText = $@"CREATE TABLE IF NOT EXISTS {schemaName}.{tableName}( + id bigserial PRIMARY KEY, + document_uuid UUID NOT NULL, + project_name VARCHAR NOT NULL, + resource_name VARCHAR NOT NULL, + resource_version VARCHAR NOT NULL, + is_descriptor BOOLEAN NOT NULL, + edfi_doc JSONB NOT NULL);"; + await cmd.ExecuteNonQueryAsync(); + } + + public static async Task InsertDocument(InsertRequest insertRequest) + { + await TryCreateTable(insertRequest.ResourceInfo); + + // Note: ProjectName and ResourceName have been validated against the ApiSchema, so SQL injection is not a concern here + string schemaName = insertRequest.ResourceInfo.ProjectName.Value.Replace("-", ""); + string tableName = insertRequest.ResourceInfo.ResourceName.Value.Replace("-", ""); + var (documentUuid, resourceInfo, edfiDoc) = insertRequest; + + // Add the new documentUuid to the document + edfiDoc.Add(new JProperty("id", documentUuid.Value)); + + using var con = new NpgsqlConnection(connectionString); + con.Open(); + + var commandText = $@" INSERT INTO {schemaName}.{tableName} + (document_uuid, project_name, resource_name, resource_version, is_descriptor, edfi_doc) + VALUES ($1, $2, $3, $4, $5, $6)"; + await using var cmd = new NpgsqlCommand(commandText, con) + { + Parameters = { + new() {Value = new Guid(documentUuid.Value), NpgsqlDbType = NpgsqlDbType.Uuid }, + new() {Value = resourceInfo.ProjectName.Value }, + new() {Value = resourceInfo.ResourceName.Value }, + new() {Value = resourceInfo.ResourceVersion.Value }, + new() {Value = resourceInfo.IsDescriptor }, + new() {Value = JsonConvert.SerializeObject(edfiDoc), NpgsqlDbType = NpgsqlDbType.Jsonb } + } + }; + await cmd.ExecuteNonQueryAsync(); + } + + public static async Task FindDocumentByDocumentUuid(GetRequest getRequest) + { + await TryCreateTable(getRequest.ResourceInfo); + + // Note: ProjectName and ResourceName have been validated against the ApiSchema, so SQL injection is not a concern here + string schemaName = getRequest.ResourceInfo.ProjectName.Value.Replace("-", ""); + string tableName = getRequest.ResourceInfo.ResourceName.Value.Replace("-", ""); + + using var con = new NpgsqlConnection(connectionString); + con.Open(); + + var commandText = $@"SELECT edfi_doc FROM {schemaName}.{tableName} WHERE document_uuid = $1;"; + + await using var cmd = new NpgsqlCommand(commandText, con) + { + Parameters = { + new() { Value = new Guid(getRequest.DocumentUuid.Value), NpgsqlDbType = NpgsqlDbType.Uuid } + } + }; + + var result = await cmd.ExecuteScalarAsync(); + return new(result == null ? "" : result.ToString() ?? ""); + } +} diff --git a/Meadowlark.net/Meadowlark.Net.Core/Backend/Postgresql/GetById.cs b/Meadowlark.net/Meadowlark.Net.Core/Backend/Postgresql/GetById.cs new file mode 100644 index 00000000..c76a1e8d --- /dev/null +++ b/Meadowlark.net/Meadowlark.Net.Core/Backend/Postgresql/GetById.cs @@ -0,0 +1,13 @@ +using Meadowlark.Net.Core.Backend.Model; +using static Meadowlark.Net.Core.Backend.Postgresql.Db; + +namespace Meadowlark.Net.Core.Backend.Postgresql; + +public static class GetById +{ + public static async Task GetByIdDb(GetRequest getRequest) + { + GetResponse result = await FindDocumentByDocumentUuid(getRequest); + return new(StatusCode: 200, Body: $"Success: {result.EdfiDoc}"); + } +} diff --git a/Meadowlark.net/Meadowlark.Net.Core/Backend/Postgresql/Upsert.cs b/Meadowlark.net/Meadowlark.Net.Core/Backend/Postgresql/Upsert.cs new file mode 100644 index 00000000..f0962b69 --- /dev/null +++ b/Meadowlark.net/Meadowlark.Net.Core/Backend/Postgresql/Upsert.cs @@ -0,0 +1,13 @@ +using Meadowlark.Net.Core.Backend.Model; +using static Meadowlark.Net.Core.Backend.Postgresql.Db; + +namespace Meadowlark.Net.Core.Backend.Postgresql; + +public static class Upsert +{ + public static async Task UpsertDb(InsertRequest insertRequest) + { + await InsertDocument(insertRequest); + return new(StatusCode: 200, Body: $"Success: {insertRequest.ToString()}"); + } +} diff --git a/Meadowlark.net/Meadowlark.Net.Core/Backend/Utility.cs b/Meadowlark.net/Meadowlark.Net.Core/Backend/Utility.cs new file mode 100644 index 00000000..6a514685 --- /dev/null +++ b/Meadowlark.net/Meadowlark.Net.Core/Backend/Utility.cs @@ -0,0 +1,23 @@ +namespace Meadowlark.Net.Backend.Postgresql; + +public static class Utility +{ + + /** + * Simple only-once guard for a function with no parameters + */ + public static Action Once(Action fn){ + var called = false; + + void guardedFunction() + { + if (!called) + { + fn(); + called = true; + } + } + + return guardedFunction; + } +} diff --git a/Meadowlark.net/Meadowlark.Net.Core/Handler/FrontendFacade.cs b/Meadowlark.net/Meadowlark.Net.Core/Handler/FrontendFacade.cs index 494e7858..92ab314c 100644 --- a/Meadowlark.net/Meadowlark.Net.Core/Handler/FrontendFacade.cs +++ b/Meadowlark.net/Meadowlark.Net.Core/Handler/FrontendFacade.cs @@ -4,6 +4,10 @@ using static Meadowlark.Net.Core.Middleware.ParsePathMiddleware; using static Meadowlark.Net.Core.Middleware.ValidateEndpointMiddleware; using static Meadowlark.Net.Core.Middleware.ValidateDocumentMiddleware; +using static Meadowlark.Net.Core.Backend.Postgresql.Upsert; +using static Meadowlark.Net.Core.Backend.Postgresql.GetById; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; namespace Meadowlark.Net.Core; @@ -21,13 +25,44 @@ public static async Task UpsertCore(FrontendRequest frontendRe ApiSchema: No.ApiSchema, ResourceSchema: No.ResourceSchema); MiddlewareModel middlewareModel = new(RequestModel: requestModel, FrontendResponse: null); - var (finalRequestModel, frontendResponse) = await middlewareModel + var (finalRequestModel, frontendResponse) = middlewareModel .SendTo(LoadApiSchema) .AndThen(ParsePath) .AndThen(EndpointValidation) .AndThen(DocumentValidation); - return frontendResponse ?? new(StatusCode: 200, Body: $"Success: {finalRequestModel.ResourceInfo.ToString()}"); + // if there is a response posted by the stack, we are done + if (frontendResponse != null) return frontendResponse; + + DocumentUuid documentUuid = new(Guid.NewGuid().ToString()); + return await UpsertDb(new(documentUuid, finalRequestModel.ResourceInfo, frontendRequest.Body ?? new JObject())); + } + catch (Exception) + { + return new(StatusCode: 500, Body: "Fail"); + } + } + + /** + * Entry point for API get by id actions + */ + public static async Task GetByIdCore(FrontendRequest frontendRequest) + { + try + { + RequestModel requestModel = new(FrontendRequest: frontendRequest, PathComponents: No.PathComponents, ResourceInfo: No.ResourceInfo, + ApiSchema: No.ApiSchema, ResourceSchema: No.ResourceSchema); + MiddlewareModel middlewareModel = new(RequestModel: requestModel, FrontendResponse: null); + + var (finalRequestModel, frontendResponse) = middlewareModel + .SendTo(LoadApiSchema) + .AndThen(ParsePath) + .AndThen(EndpointValidation); + + // if there is a response posted by the stack, we are done + if (frontendResponse != null) return frontendResponse; + + return await GetByIdDb(new(finalRequestModel.PathComponents.DocumentUuid ?? new(""), finalRequestModel.ResourceInfo)); } catch (Exception) { diff --git a/Meadowlark.net/Meadowlark.Net.Core/Meadowlark.Net.Core.csproj b/Meadowlark.net/Meadowlark.Net.Core/Meadowlark.Net.Core.csproj index 2258f9e2..4167b236 100644 --- a/Meadowlark.net/Meadowlark.Net.Core/Meadowlark.Net.Core.csproj +++ b/Meadowlark.net/Meadowlark.Net.Core/Meadowlark.Net.Core.csproj @@ -9,7 +9,8 @@ - + + @@ -18,4 +19,10 @@ + + + Always + + + diff --git a/Meadowlark.net/Meadowlark.Net.Core/Middleware/ValidateDocumentMiddleware.cs b/Meadowlark.net/Meadowlark.Net.Core/Middleware/ValidateDocumentMiddleware.cs index 437a1e6a..1443eecd 100644 --- a/Meadowlark.net/Meadowlark.Net.Core/Middleware/ValidateDocumentMiddleware.cs +++ b/Meadowlark.net/Meadowlark.Net.Core/Middleware/ValidateDocumentMiddleware.cs @@ -8,7 +8,7 @@ public static class ValidateDocumentMiddleware /** * Validates JSON document shape */ - public static async Task DocumentValidation(MiddlewareModel middlewareModel) + public static MiddlewareModel DocumentValidation(MiddlewareModel middlewareModel) { var (requestModel, frontendResponse) = middlewareModel; diff --git a/Meadowlark.net/Meadowlark.Net.Frontend.MinimalAPI/CrudHandler.cs b/Meadowlark.net/Meadowlark.Net.Frontend.MinimalAPI/CrudHandler.cs index 0710b585..0baf5f74 100644 --- a/Meadowlark.net/Meadowlark.Net.Frontend.MinimalAPI/CrudHandler.cs +++ b/Meadowlark.net/Meadowlark.Net.Frontend.MinimalAPI/CrudHandler.cs @@ -29,11 +29,9 @@ public static class CrudHandler */ public static async Task Upsert(HttpRequest request) { - // respondWith(await meadowlarkUpsert(fromRequest(request)), reply); - JObject? body = await ExtractJsonBodyFrom(request); - FrontendRequest frontendRequest = new(Action: "POST", Body: body, Path: request.Path, TraceId: System.Guid.NewGuid().ToString()); + FrontendRequest frontendRequest = new(Action: "POST", Body: body, Path: request.Path, TraceId: Guid.NewGuid().ToString()); var frontendResponse = await UpsertCore(frontendRequest); @@ -43,11 +41,13 @@ public static async Task Upsert(HttpRequest request) /** * Entry point for all API GET requests */ - public static IResult Get(HttpRequest request) + public static async Task GetById(HttpRequest request) { - // respondWith(await meadowlarkGet(fromRequest(request)), reply); + FrontendRequest frontendRequest = new(Action: "GET", Body: null, Path: request.Path, TraceId: Guid.NewGuid().ToString()); + + var frontendResponse = await GetByIdCore(frontendRequest); - return Results.Ok(new { Message = "Get" }); + return Results.Content(statusCode: frontendResponse.StatusCode, content: frontendResponse.Body); } /** diff --git a/Meadowlark.net/Meadowlark.Net.Frontend.MinimalAPI/Program.cs b/Meadowlark.net/Meadowlark.Net.Frontend.MinimalAPI/Program.cs index 65b25929..95aaab08 100644 --- a/Meadowlark.net/Meadowlark.Net.Frontend.MinimalAPI/Program.cs +++ b/Meadowlark.net/Meadowlark.Net.Frontend.MinimalAPI/Program.cs @@ -4,7 +4,7 @@ var app = builder.Build(); app.MapPost("/{**catchAll}", Upsert); -app.MapGet("/", Get); +app.MapGet("/{**catchAll}", GetById); app.MapPut("/", Update); app.MapDelete("/", DeleteIt); diff --git a/Meadowlark.net/Meadowlark.Net.sln b/Meadowlark.net/Meadowlark.Net.sln index 3cb87ee6..72c1bc5b 100644 --- a/Meadowlark.net/Meadowlark.Net.sln +++ b/Meadowlark.net/Meadowlark.Net.sln @@ -24,5 +24,9 @@ Global {94432F90-DE9C-4E69-A4DD-D4EEA31DE5C3}.Debug|Any CPU.Build.0 = Debug|Any CPU {94432F90-DE9C-4E69-A4DD-D4EEA31DE5C3}.Release|Any CPU.ActiveCfg = Release|Any CPU {94432F90-DE9C-4E69-A4DD-D4EEA31DE5C3}.Release|Any CPU.Build.0 = Release|Any CPU + {F7B239AD-5A50-49FB-A6C7-36B40484CB6E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F7B239AD-5A50-49FB-A6C7-36B40484CB6E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F7B239AD-5A50-49FB-A6C7-36B40484CB6E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F7B239AD-5A50-49FB-A6C7-36B40484CB6E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/Meadowlark.net/RestClient/test.http b/Meadowlark.net/RestClient/test.http index 2d0b0ec8..5d829d8e 100644 --- a/Meadowlark.net/RestClient/test.http +++ b/Meadowlark.net/RestClient/test.http @@ -9,6 +9,12 @@ POST http://localhost:3000/ed-fi/contentClassDescriptors/ "namespace": "uri://ed-fi.org/ContentClassDescriptor" } +### Test GET of descriptor + +GET http://localhost:3000/ed-fi/contentClassDescriptors/805e77ca-d917-4706-a0d4-8c36f2281d8b + + + ### Test POST of an EducationContent POST http://localhost:3000/ed-fi/educationContents @@ -21,6 +27,11 @@ POST http://localhost:3000/ed-fi/educationContents "learningResourceMetadataURI": "21430" } +### Test GET of an EducationContent + +GET http://localhost:3000/ed-fi/educationContents/2ddb19fb-9563-44d6-bced-b8218665258b + + ### Test POST of an EducationContent with two overposted fields ### Result is NoAdditionalPropertiesAllowed POST http://localhost:3000/ed-fi/educationContents