Skip to content

Commit 126e47d

Browse files
feat: fix complete interface, change resp to res, improve test coverage, add exists to buckets (#23)
* fix return type of task complete * add file event worker * change resp to res * improve test coverage * improve consistency of permissions * fix late initialisation of workers * consolidate channel handling
1 parent 370cdb4 commit 126e47d

Some content is hidden

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

60 files changed

+2186
-546
lines changed

README.md

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,9 @@ The SDK is in early stage development and APIs and interfaces are still subject
3434

3535
# Building a REST API with Nitric
3636

37-
This guide will show you how to build a serverless REST API with the Nitric framework using Dart. The example API enables reading, writing and editing basic user profile information using a Nitric [collection](https://nitric.io/docs/collections) to store user data. Once the API is created we'll test it locally, then optionally deploy it to a cloud of your choice.
37+
This guide will show you how to build a serverless REST API with the Nitric framework using Dart. The example API enables reading, writing and editing basic user profile information using a Nitric [key value store](https://nitric.io/docs/keyvalue) to store user data. Once the API is created we'll test it locally, then optionally deploy it to a cloud of your choice.
3838

39-
The example API enables reading, writing, and deleting profile information from a Nitric [collection](https://nitric.io/docs/collection).
39+
The example API enables reading, writing, and deleting profile information from a Nitric [key value store](https://nitric.io/docs/keyvalue).
4040

4141
The API will provide the following routes:
4242

@@ -72,10 +72,8 @@ dart create -t console my-profile-api
7272
Add the Nitric SDK by adding the repository URL to your `pubspec.yaml`.
7373

7474
```yaml
75-
nitric_sdk:
76-
git:
77-
url: https://github.com/nitrictech/dart-sdk.git
78-
ref: main
75+
dependencies:
76+
nitric_sdk: ^1.2.0
7977
```
8078
8179
Next, open the project in your editor of choice.
@@ -112,7 +110,7 @@ services:
112110
113111
## Create a Profile class
114112
115-
We will create a class to represent the profiles that we will store in the collection. We will add `toJson` and `fromJson` functions to assist.
113+
We will create a class to represent the profiles that we will store in the key value store. We will add `toJson` and `fromJson` functions to assist.
116114

117115
```dart
118116
class Profile {
@@ -143,19 +141,18 @@ Applications built with Nitric can contain many APIs, let's start by adding one
143141
```dart
144142
import 'package:nitric_sdk/nitric.dart';
145143
import 'package:nitric_sdk/resources.dart';
146-
import 'package:nitric_sdk/src/context/common.dart';
147144
148145
import 'package:uuid/uuid.dart';
149146
150147
void main() {
151148
// Create an API named 'public'
152-
final profileApi = api("public");
149+
final profileApi = Nitric.api("public");
153150
154-
// Define a key value store named 'profiles', then request getting, setting and deleting permissions.
155-
final profiles = store("profiles").requires([
156-
KeyValuePermission.getting,
157-
KeyValuePermission.setting,
158-
KeyValuePermission.deleting
151+
// Define a key value store named 'profiles', then request get, set and delete permissions.
152+
final profiles = Nitric.kv("profiles").allow([
153+
KeyValuePermission.get,
154+
KeyValuePermission.set,
155+
KeyValuePermission.delete
159156
]);
160157
}
161158
```
@@ -188,7 +185,7 @@ profileApi.post("/profiles", (ctx) async {
188185
189186
final profile = Profile.fromJson(ctx.req.json());
190187
191-
// Store the new profile in the profiles collection
188+
// Store the new profile in the profiles kv store
192189
await profiles.set(id, profile);
193190
194191
// Send a success response.
@@ -335,7 +332,7 @@ If you want to go a bit deeper and create some other resources with Nitric, why
335332
Define a bucket named `profilesImg` with reading/writing permissions.
336333

337334
```dart
338-
final profilesImg = Nitric.bucket("profilesImg").requires([BucketPermission.reading, BucketPermission.writing]);
335+
final profilesImg = Nitric.bucket("profilesImg").allow([BucketPermission.read, BucketPermission.write]);
339336
```
340337

341338
### Get a URL to upload a profile image

analysis_options.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,17 @@ include: package:lints/recommended.yaml
1515

1616
# Uncomment the following section to specify additional rules.
1717

18-
# linter:
19-
# rules:
20-
# - camel_case_types
18+
linter:
19+
rules:
20+
- camel_case_types
21+
- unawaited_futures
2122

2223
analyzer:
2324
exclude:
2425
- lib/**/*.pb.dart
2526
- lib/**/*.pbenum.dart
2627
- lib/**/*.pbgrpb.dart
2728
- lib/**/*.pbjson.dart
28-
2929
# For more information about the core and recommended set of lints, see
3030
# https://dart.dev/go/core-lints
3131

example/pubspec.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ environment:
77
sdk: ^3.2.5
88

99
dependencies:
10-
nitric_sdk: ^1.0.0
10+
nitric_sdk:
11+
path: ../
1112
uuid: ^4.3.3
1213

1314
dev_dependencies:

example/services/nitric_example.dart

Lines changed: 17 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -21,26 +21,18 @@ class Profile {
2121
}
2222

2323
void main() {
24-
var oidc = Nitric.oidcRule(
25-
"profile security",
26-
"https://dev-w7gm5ldb.us.auth0.com",
27-
["https://test-security-definition/"]);
28-
2924
// Create an API named 'public'
30-
final profileApi = Nitric.api("public",
31-
opts: ApiOptions(security: [
32-
oidc(["user:read"])
33-
]));
25+
final profileApi = Nitric.api("public");
3426

3527
// Define a collection named 'profiles', then request reading and writing permissions.
36-
final profiles = Nitric.store("profiles").requires([
37-
KeyValueStorePermission.getting,
38-
KeyValueStorePermission.deleting,
39-
KeyValueStorePermission.setting
28+
final profiles = Nitric.kv("profiles").allow([
29+
KeyValueStorePermission.get,
30+
KeyValueStorePermission.delete,
31+
KeyValueStorePermission.set
4032
]);
4133

4234
final profilesImg = Nitric.bucket("profilesImg")
43-
.requires([BucketPermission.reading, BucketPermission.writing]);
35+
.allow([BucketPermission.read, BucketPermission.write]);
4436

4537
profileApi.post("/profiles", (ctx) async {
4638
final uuid = Uuid();
@@ -53,10 +45,10 @@ void main() {
5345
await profiles.set(id, profile.toJson());
5446

5547
// Send a success response.
56-
ctx.resp.body = "Profile $id created.";
48+
ctx.res.body = "Profile $id created.";
5749
} on Exception catch (e) {
58-
ctx.resp.status = 400;
59-
ctx.resp.body = "An error occurred: $e";
50+
ctx.res.status = 400;
51+
ctx.res.body = "An error occurred: $e";
6052
}
6153

6254
return ctx;
@@ -68,11 +60,11 @@ void main() {
6860
try {
6961
// Retrieve and return the profile data
7062
final profile = await profiles.get(id);
71-
ctx.resp.json(profile);
63+
ctx.res.json(profile);
7264
} on Exception catch (e) {
7365
print(e);
74-
ctx.resp.status = 404;
75-
ctx.resp.body = "Profile $id not found.";
66+
ctx.res.status = 404;
67+
ctx.res.body = "Profile $id not found.";
7668
}
7769

7870
return ctx;
@@ -84,10 +76,10 @@ void main() {
8476
// Delete the profile
8577
try {
8678
await profiles.delete(id);
87-
ctx.resp.body = "Profile $id removed.";
79+
ctx.res.body = "Profile $id removed.";
8880
} on Exception catch (e) {
89-
ctx.resp.status = 404;
90-
ctx.resp.body = "Profile $id not found. $e";
81+
ctx.res.status = 404;
82+
ctx.res.body = "Profile $id not found. $e";
9183
}
9284

9385
return ctx;
@@ -124,8 +116,8 @@ void main() {
124116
final photoUrl =
125117
await profilesImg.file("images/$id/photo.png").getDownloadUrl();
126118

127-
ctx.resp.status = 303;
128-
ctx.resp.headers["Location"] = [photoUrl];
119+
ctx.res.status = 303;
120+
ctx.res.headers["Location"] = [photoUrl];
129121

130122
return ctx;
131123
});

lib/api.dart

Lines changed: 0 additions & 1 deletion
This file was deleted.

lib/nitric.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
library;
22

33
export 'src/nitric.dart';
4+
export 'src/context/common.dart';

lib/resources.dart

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
1-
export 'src/resources/resources.dart';
2-
export 'src/context/common.dart';
1+
export 'src/resources/common.dart';

lib/src/api/bucket.dart

Lines changed: 13 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,31 @@
11
import 'dart:async';
22
import 'dart:convert';
33

4-
import 'package:nitric_sdk/nitric.dart';
54
import 'package:nitric_sdk/src/context/common.dart';
65
import 'package:nitric_sdk/src/grpc_helper.dart';
76
import 'package:nitric_sdk/src/nitric/proto/storage/v1/storage.pbgrpc.dart'
87
as $p;
98
import 'package:nitric_sdk/src/google/protobuf/duration.pb.dart' as $d;
109
import 'package:fixnum/fixnum.dart';
11-
import 'package:grpc/grpc.dart';
10+
import 'package:nitric_sdk/src/workers/common.dart';
1211

1312
class Bucket {
1413
late $p.StorageClient _storageClient;
14+
late $p.StorageListenerClient? _storageListenerClient;
1515

1616
String name;
1717

18-
Bucket(this.name, {$p.StorageClient? client}) {
18+
Bucket(this.name,
19+
{$p.StorageClient? client,
20+
$p.StorageListenerClient? storageListenerClient}) {
1921
if (client == null) {
20-
final channel = createClientChannelFromEnvVar();
21-
22-
_storageClient = $p.StorageClient(channel);
22+
_storageClient =
23+
$p.StorageClient(ClientChannelSingleton.instance.clientChannel);
2324
} else {
2425
_storageClient = client;
2526
}
27+
28+
_storageListenerClient = storageListenerClient;
2629
}
2730

2831
/// Get a reference to a file by it's [key].
@@ -42,7 +45,7 @@ class Bucket {
4245

4346
/// Create a blob event subscription triggered on the [blobEventType] filtered by files that match the [keyPrefixFilter].
4447
Future<void> on(BlobEventType blobEventType, String keyPrefixFilter,
45-
BlobEventHandler handler) async {
48+
FileEventHandler handler) async {
4649
// Create the request to register the Storage listener with the membrane
4750
final eventType = switch (blobEventType) {
4851
BlobEventType.write => $p.BlobEventType.Created,
@@ -55,9 +58,10 @@ class Bucket {
5558
blobEventType: eventType,
5659
);
5760

58-
var worker = BlobEventWorker(registrationRequest, handler, this);
61+
var worker = FileEventWorker(registrationRequest, handler, this,
62+
client: _storageListenerClient);
5963

60-
worker.start();
64+
await worker.start();
6165
}
6266
}
6367

@@ -149,50 +153,3 @@ class File {
149153
return resp.url;
150154
}
151155
}
152-
153-
class BlobEventWorker implements Worker {
154-
$p.RegistrationRequest registrationRequest;
155-
BlobEventHandler middleware;
156-
Bucket bucket;
157-
158-
BlobEventWorker(this.registrationRequest, this.middleware, this.bucket);
159-
160-
@override
161-
Future<void> start() async {
162-
// Create Storage listener client
163-
final channel = createClientChannelFromEnvVar();
164-
final client = $p.StorageListenerClient(channel);
165-
166-
final initMsg = $p.ClientMessage(registrationRequest: registrationRequest);
167-
168-
// Create the request stream and send the initial message
169-
final requestStream = StreamController<$p.ClientMessage>();
170-
requestStream.add(initMsg);
171-
172-
final response = client.listen(
173-
requestStream.stream,
174-
);
175-
176-
await for (final msg in response) {
177-
if (msg.hasRegistrationResponse()) {
178-
// Blob Notification has connected with Nitric server
179-
} else if (msg.hasBlobEventRequest()) {
180-
var ctx = BlobEventContext.fromRequest(msg, bucket);
181-
182-
try {
183-
ctx = await middleware(ctx);
184-
} on GrpcError catch (e) {
185-
print("caught a GrpcError: $e");
186-
} catch (e) {
187-
print("unhandled application error: $e");
188-
189-
ctx.resp.success = false;
190-
}
191-
192-
requestStream.add(ctx.toResponse());
193-
}
194-
}
195-
196-
await channel.shutdown();
197-
}
198-
}

lib/src/api/keyvalue.dart

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,8 @@ class KeyValueStore {
1111

1212
KeyValueStore(this.name, {$p.KvStoreClient? client}) {
1313
if (client == null) {
14-
var channel = createClientChannelFromEnvVar();
15-
16-
_keyValueClient = $p.KvStoreClient(channel);
14+
_keyValueClient =
15+
$p.KvStoreClient(ClientChannelSingleton.instance.clientChannel);
1716
} else {
1817
_keyValueClient = client;
1918
}

lib/src/api/queue.dart

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import 'dart:async';
22

3-
import 'package:nitric_sdk/api.dart';
3+
import 'package:nitric_sdk/src/api/api.dart';
44
import 'package:nitric_sdk/src/grpc_helper.dart';
55
import 'package:nitric_sdk/src/nitric/proto/queues/v1/queues.pbgrpc.dart' as $p;
66

@@ -13,9 +13,8 @@ class Queue {
1313
/// Construct a new queue.
1414
Queue(this.name, {$p.QueuesClient? client}) {
1515
if (client == null) {
16-
final channel = createClientChannelFromEnvVar();
17-
18-
_queuesClient = $p.QueuesClient(channel);
16+
_queuesClient =
17+
$p.QueuesClient(ClientChannelSingleton.instance.clientChannel);
1918
} else {
2019
_queuesClient = client;
2120
}
@@ -61,7 +60,7 @@ class DequeuedMessage {
6160
}
6261

6362
/// Inform the queue that the message was handled successfully.
64-
void complete() async {
63+
Future<void> complete() async {
6564
var req =
6665
$p.QueueCompleteRequest(leaseId: _leaseId, queueName: _queue.name);
6766

0 commit comments

Comments
 (0)