Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit d271192

Browse files
committed
Merge branch 'v6.0'
2 parents c770294 + 96ff616 commit d271192

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+7771
-274
lines changed

‎.github/workflows/azure-static-web-apps-declarative-db-deploy.yml.sample

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ name: Azure Static Web Apps CI/CD
33
on:
44
push:
55
branches:
6-
- main
6+
- v6.0
77
pull_request:
88
types: [opened, synchronize, reopened, closed]
99
branches:
10-
- main
10+
- v6.0
1111

1212
jobs:
1313
build_and_deploy_job:
@@ -22,7 +22,7 @@ jobs:
2222
uses: azure/sql-action@v1.3
2323
with:
2424
connection-string: ${{ secrets.AZURE_SQL_CONNECTION_STRING }}
25-
project-file: './database/declarative-deploy/todo_v5/todo_v5.sqlproj'
25+
project-file: './database/declarative-deploy/todo_v6/todo_v6.sqlproj'
2626
build-arguments: '-c Release'
2727
- name: Build And Deploy
2828
id: builddeploy

‎.github/workflows/azure-static-web-apps-imperative-db-deploy.yml.sample

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ name: Azure Static Web Apps CI/CD
33
on:
44
push:
55
branches:
6-
- main
6+
- v6.0
77
pull_request:
88
types: [opened, synchronize, reopened, closed]
99
branches:
10-
- main
10+
- v6.0
1111

1212
jobs:
1313
build_and_deploy_job:

‎README.md

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ Taxonomies for products and languages: https://review.docs.microsoft.com/new-hop
3232

3333
![License](https://img.shields.io/badge/license-MIT-green.svg)
3434

35-
Learn how to implement a fully working, end-to-end, full-stack solution using Azure Static Web Apps, Azure Functions and Azure SQL Serverless. In this session we’ll see and build together the simple (but not too simple!) To-Do list reference app, using Vue.js, CI/CD and more!
35+
Learn how to implement a fully working, end-to-end, full-stack solution using Azure Static Web Apps, Azure Functions and Azure SQL Serverless. In this session we’ll see and build together the simple (but not too simple!) To-Do list reference app, using Vue.js, CI/CD and more!
3636

3737
## Azure Serverless Conference Recording
3838

@@ -55,12 +55,8 @@ This repo has different branches that shows the development at different stages.
5555
- 2.0: Database support added
5656
- 3.0: Authentication and Authorization via EasyAuth
5757
- 4.0: Resilient connections using Polly
58-
- 5.0: [This Branch] Database imperative or declarative CI/CD
59-
- 6.0: (Work in progress) Make proper use of Vue.Js 3 for the frontend
60-
- 7.0: (Not started yet) Stop using passwords to allow the backend connect to the database
61-
- 8.0: (Not started yet) Use FusionCache to add caching support
62-
- 9.0: (Not started yet) Implement [Massive Read-Scale Out pattern](https://github.com/Azure-Samples/azure-sql-db-named-replica-oltp-scaleout)
63-
58+
- 5.0: Database imperative or declarative CI/CD
59+
- 6.0: [This Branch] Make proper use of Vue.Js 3 for the frontend
6460

6561
### V5.0 Notes
6662

@@ -76,12 +72,24 @@ In this branch the backend REST API service and the database are modified so tha
7672

7773
### V2.0 Notes
7874

79-
In this branch the backend REST API service is modified so that the to-do list can be saved an manged using an Azure SQL database. Communication with the database is done using JSON too, as Azure SQL support [JSON natively](https://docs.microsoft.com/sql/relational-databases/json/json-data-sql-server).
75+
In this branch the backend REST API service is modified so that the to-do list can be saved an managed using an Azure SQL database. Communication with the database is done using JSON too, as Azure SQL support [JSON natively](https://docs.microsoft.com/en-us/sql/relational-databases/json/json-data-sql-server?view=sql-server-ver15). As an alternative, the same API using code [EF Core](https://learn.microsoft.com/en-us/ef/core/) is also provided.
76+
A initial option to (imperatively) deploy the database is also used, either using manually applied database script, or via the EF Core migrations.
8077

8178
### V1.0 Notes
8279

8380
In this branch the solution will have a full working front-end, sending REST request to the fully working backend REST API. The to-do list is saved in-memory using a List object. No authentication or authorization is supported.
8481

82+
## Future Roadmap
83+
84+
- Add API unit testing using [Mocha](https://mochajs.org/)
85+
- Add API smoke tests using [Locust](https://locust.io/)
86+
87+
Future additional branches:
88+
89+
- 7.0: Stop using passwords to allow the backend connect to the database
90+
- 8.0: Use FusionCache to add caching support
91+
- 9.0: Implement [Massive Read-Scale Out pattern](https://github.com/Azure-Samples/azure-sql-db-named-replica-oltp-scaleout)
92+
8593
## Folder Structure
8694

8795
- `/api`: the NodeJs Azure Function code used to provide the backend API, called by the Vue.Js client.

‎api/Startup.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using System;
2+
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
3+
using Microsoft.Extensions.DependencyInjection;
4+
using Microsoft.EntityFrameworkCore;
5+
using Todo.Backend.EFCore;
6+
7+
[assembly: FunctionsStartup(typeof(Todo.Backend.Startup))]
8+
9+
namespace Todo.Backend
10+
{
11+
public class Startup : FunctionsStartup
12+
{
13+
public override void Configure(IFunctionsHostBuilder builder)
14+
{
15+
string connectionString = Environment.GetEnvironmentVariable("AzureSQL");
16+
builder.Services.AddDbContext<TodoContext>(
17+
options => options.UseSqlServer(connectionString)
18+
);
19+
}
20+
}
21+
}

‎api/ToDoHandler.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
using Polly;
1919
using System.ComponentModel;
2020

21-
namespace api
21+
namespace Todo.Backend
2222
{
2323
public static class ToDoHandler
2424
{

‎api/ToDoHandlerEFCore.cs

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
using System;
2+
using System.IO;
3+
using System.Threading.Tasks;
4+
using Microsoft.AspNetCore.Mvc;
5+
using Microsoft.Azure.WebJobs;
6+
using Microsoft.Azure.WebJobs.Extensions.Http;
7+
using Microsoft.AspNetCore.Http;
8+
using Microsoft.Extensions.Logging;
9+
using Newtonsoft.Json;
10+
using Newtonsoft.Json.Linq;
11+
using System.Collections.Generic;
12+
using System.Linq;
13+
using System.Data;
14+
using Microsoft.Data.SqlClient;
15+
using Microsoft.EntityFrameworkCore;
16+
using Microsoft.EntityFrameworkCore.Design;
17+
using System.ComponentModel.DataAnnotations.Schema;
18+
using System.Security.Claims;
19+
using System.Text;
20+
21+
namespace Todo.Backend.EFCore
22+
{
23+
[Table("todos")]
24+
public class Todo {
25+
26+
[JsonProperty("id")]
27+
[Column("id")]
28+
public int Id { get; set; }
29+
30+
[JsonProperty("title")]
31+
[Column("todo", TypeName = "nvarchar(100)")]
32+
public string Title { get; set; }
33+
34+
[JsonProperty("completed")]
35+
[Column("completed", TypeName = "tinyint")]
36+
public bool Completed { get; set; }
37+
38+
[Column("owner_id", TypeName = "nvarchar(128)")]
39+
public string Owner { get; set; }
40+
41+
public bool ShouldSerializeOwner() => false;
42+
43+
}
44+
45+
public class TodoContext : DbContext
46+
{
47+
public TodoContext(DbContextOptions<TodoContext> options)
48+
: base(options)
49+
{ }
50+
51+
protected override void OnModelCreating(ModelBuilder modelBuilder)
52+
{
53+
modelBuilder.HasSequence<int>("global_sequence");
54+
55+
modelBuilder.Entity<Todo>()
56+
.Property(o => o.Id)
57+
.HasDefaultValueSql("NEXT VALUE FOR global_sequence");
58+
59+
modelBuilder.Entity<Todo>()
60+
.Property(o => o.Owner)
61+
.HasDefaultValue("anonymous");
62+
}
63+
64+
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
65+
=> optionsBuilder
66+
.LogTo(Console.WriteLine, LogLevel.Information)
67+
.EnableSensitiveDataLogging()
68+
.EnableDetailedErrors();
69+
70+
public DbSet<Todo> Todos { get; set; }
71+
}
72+
73+
public class TodoContextFactory: IDesignTimeDbContextFactory<TodoContext>
74+
{
75+
public TodoContext CreateDbContext(string[] args)
76+
{
77+
var optionsBuilder = new DbContextOptionsBuilder<TodoContext>();
78+
optionsBuilder.UseSqlServer(Environment.GetEnvironmentVariable("AzureSQL"));
79+
80+
return new TodoContext(optionsBuilder.Options);
81+
}
82+
}
83+
84+
public class ClientPrincipal
85+
{
86+
public string IdentityProvider { get; set; }
87+
public string UserId { get; set; }
88+
public string UserDetails { get; set; }
89+
public IEnumerable<string> UserRoles { get; set; }
90+
}
91+
92+
public static class Utils
93+
{
94+
public static ClientPrincipal ParsePrincipal(this HttpRequest req)
95+
{
96+
var principal = new ClientPrincipal();
97+
98+
if (req.Headers.TryGetValue("x-ms-client-principal", out var header))
99+
{
100+
var data = header[0];
101+
var decoded = Convert.FromBase64String(data);
102+
var json = Encoding.UTF8.GetString(decoded);
103+
principal = JsonConvert.DeserializeObject<ClientPrincipal>(json);
104+
}
105+
106+
principal.UserRoles = principal.UserRoles?.Except(new string[] { "anonymous" }, StringComparer.CurrentCultureIgnoreCase);
107+
principal.UserId = principal.UserId ?? "anonymous";
108+
109+
return principal;
110+
}
111+
}
112+
113+
public class ToDoHandler
114+
{
115+
private TodoContext _todoContext;
116+
117+
public ToDoHandler(TodoContext todoContext)
118+
{
119+
this._todoContext = todoContext;
120+
}
121+
122+
[FunctionName("GetEF")]
123+
public async Task<IActionResult> Get(
124+
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "ef/todo/{id:int?}")] HttpRequest req,
125+
ILogger log,
126+
int? id)
127+
{
128+
var cp = req.ParsePrincipal();
129+
130+
IQueryable<Todo> todos = this._todoContext.Todos.Where(t => t.Owner == cp.UserId);
131+
132+
if (id.HasValue) {
133+
todos = this._todoContext.Todos.Where(t => t.Id == id);
134+
}
135+
136+
return new OkObjectResult(await todos.ToListAsync());
137+
}
138+
139+
[FunctionName("PostEF")]
140+
public async Task<IActionResult> Post(
141+
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "ef/todo")] HttpRequest req,
142+
ILogger log)
143+
{
144+
var cp = req.ParsePrincipal();
145+
146+
string body = await new StreamReader(req.Body).ReadToEndAsync();
147+
var todo = JsonConvert.DeserializeObject<Todo>(body);
148+
todo.Owner = cp.UserId;
149+
150+
await this._todoContext.AddAsync(todo);
151+
await this._todoContext.SaveChangesAsync();
152+
153+
var result = new List<Todo>() { todo };
154+
return new OkObjectResult(result);
155+
}
156+
157+
[FunctionName("PatchEF")]
158+
public async Task<IActionResult> Patch(
159+
[HttpTrigger(AuthorizationLevel.Anonymous, "patch", Route = "ef/todo/{id}")] HttpRequest req,
160+
ILogger log,
161+
int id)
162+
{
163+
var cp = req.ParsePrincipal();
164+
165+
string body = await new StreamReader(req.Body).ReadToEndAsync();
166+
var newTodo = JsonConvert.DeserializeObject<Todo>(body);
167+
168+
var targetTodo = this._todoContext.Todos.Where(t => t.Owner == cp.UserId).FirstOrDefault(t => t.Id == id);
169+
if (targetTodo == null)
170+
return new NotFoundResult();
171+
172+
targetTodo.Title = newTodo.Title ?? targetTodo.Title;
173+
targetTodo.Completed = newTodo.Completed;
174+
175+
await this._todoContext.SaveChangesAsync();
176+
177+
return new OkObjectResult(targetTodo);
178+
}
179+
180+
[FunctionName("DeleteEF")]
181+
public async Task<IActionResult> Delete(
182+
[HttpTrigger(AuthorizationLevel.Anonymous, "delete", Route = "ef/todo/{id}")] HttpRequest req,
183+
ILogger log,
184+
int id)
185+
{
186+
var cp = req.ParsePrincipal();
187+
188+
var todo = this._todoContext.Todos.Where(t => t.Owner == cp.UserId).FirstOrDefault(t => t.Id == id);
189+
190+
if (todo == null)
191+
return new NotFoundResult();
192+
193+
this._todoContext.Todos.Remove(todo);
194+
await this._todoContext.SaveChangesAsync();
195+
196+
return new OkObjectResult(todo);
197+
}
198+
}
199+
}

‎api/api.csproj

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,14 @@
55
</PropertyGroup>
66
<ItemGroup>
77
<PackageReference Include="Dapper" Version="2.0.123" />
8+
<PackageReference Include="Microsoft.Azure.Functions.Extensions" Version="1.1.0" />
89
<PackageReference Include="Microsoft.Data.SqlClient" Version="4.1.0" />
10+
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.10">
11+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
12+
<PrivateAssets>all</PrivateAssets>
13+
</PackageReference>
14+
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.10" />
15+
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" />
916
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="4.0.1" />
1017
<PackageReference Include="Polly" Version="7.2.3" />
1118
</ItemGroup>

‎api/migrations/20221111194425_InitialCreate.Designer.cs

Lines changed: 51 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /