Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: fix complete interface, change resp to res, improve test coverage, add exists to buckets #23

Merged
merged 8 commits into from
Apr 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 13 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ The SDK is in early stage development and APIs and interfaces are still subject

# Building a REST API with Nitric

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.
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.

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

The API will provide the following routes:

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

```yaml
nitric_sdk:
git:
url: https://github.com/nitrictech/dart-sdk.git
ref: main
dependencies:
nitric_sdk: ^1.2.0
```

Next, open the project in your editor of choice.
Expand Down Expand Up @@ -112,7 +110,7 @@ services:

## Create a Profile class

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.
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.

```dart
class Profile {
Expand Down Expand Up @@ -143,19 +141,18 @@ Applications built with Nitric can contain many APIs, let's start by adding one
```dart
import 'package:nitric_sdk/nitric.dart';
import 'package:nitric_sdk/resources.dart';
import 'package:nitric_sdk/src/context/common.dart';

import 'package:uuid/uuid.dart';

void main() {
// Create an API named 'public'
final profileApi = api("public");
final profileApi = Nitric.api("public");

// Define a key value store named 'profiles', then request getting, setting and deleting permissions.
final profiles = store("profiles").requires([
KeyValuePermission.getting,
KeyValuePermission.setting,
KeyValuePermission.deleting
// Define a key value store named 'profiles', then request get, set and delete permissions.
final profiles = Nitric.kv("profiles").allow([
KeyValuePermission.get,
KeyValuePermission.set,
KeyValuePermission.delete
]);
}
```
Expand Down Expand Up @@ -188,7 +185,7 @@ profileApi.post("/profiles", (ctx) async {

final profile = Profile.fromJson(ctx.req.json());

// Store the new profile in the profiles collection
// Store the new profile in the profiles kv store
await profiles.set(id, profile);

// Send a success response.
Expand Down Expand Up @@ -335,7 +332,7 @@ If you want to go a bit deeper and create some other resources with Nitric, why
Define a bucket named `profilesImg` with reading/writing permissions.

```dart
final profilesImg = Nitric.bucket("profilesImg").requires([BucketPermission.reading, BucketPermission.writing]);
final profilesImg = Nitric.bucket("profilesImg").allow([BucketPermission.read, BucketPermission.write]);
```

### Get a URL to upload a profile image
Expand Down
8 changes: 4 additions & 4 deletions analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,17 @@ include: package:lints/recommended.yaml

# Uncomment the following section to specify additional rules.

# linter:
# rules:
# - camel_case_types
linter:
rules:
- camel_case_types
- unawaited_futures

analyzer:
exclude:
- lib/**/*.pb.dart
- lib/**/*.pbenum.dart
- lib/**/*.pbgrpb.dart
- lib/**/*.pbjson.dart

# For more information about the core and recommended set of lints, see
# https://dart.dev/go/core-lints

Expand Down
3 changes: 2 additions & 1 deletion example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ environment:
sdk: ^3.2.5

dependencies:
nitric_sdk: ^1.0.0
nitric_sdk:
path: ../
uuid: ^4.3.3

dev_dependencies:
Expand Down
42 changes: 17 additions & 25 deletions example/services/nitric_example.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,26 +21,18 @@ class Profile {
}

void main() {
var oidc = Nitric.oidcRule(
"profile security",
"https://dev-w7gm5ldb.us.auth0.com",
["https://test-security-definition/"]);

// Create an API named 'public'
final profileApi = Nitric.api("public",
opts: ApiOptions(security: [
oidc(["user:read"])
]));
final profileApi = Nitric.api("public");

// Define a collection named 'profiles', then request reading and writing permissions.
final profiles = Nitric.store("profiles").requires([
KeyValueStorePermission.getting,
KeyValueStorePermission.deleting,
KeyValueStorePermission.setting
final profiles = Nitric.kv("profiles").allow([
KeyValueStorePermission.get,
KeyValueStorePermission.delete,
KeyValueStorePermission.set
]);

final profilesImg = Nitric.bucket("profilesImg")
.requires([BucketPermission.reading, BucketPermission.writing]);
.allow([BucketPermission.read, BucketPermission.write]);

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

// Send a success response.
ctx.resp.body = "Profile $id created.";
ctx.res.body = "Profile $id created.";
} on Exception catch (e) {
ctx.resp.status = 400;
ctx.resp.body = "An error occurred: $e";
ctx.res.status = 400;
ctx.res.body = "An error occurred: $e";
}

return ctx;
Expand All @@ -68,11 +60,11 @@ void main() {
try {
// Retrieve and return the profile data
final profile = await profiles.get(id);
ctx.resp.json(profile);
ctx.res.json(profile);
} on Exception catch (e) {
print(e);
ctx.resp.status = 404;
ctx.resp.body = "Profile $id not found.";
ctx.res.status = 404;
ctx.res.body = "Profile $id not found.";
}

return ctx;
Expand All @@ -84,10 +76,10 @@ void main() {
// Delete the profile
try {
await profiles.delete(id);
ctx.resp.body = "Profile $id removed.";
ctx.res.body = "Profile $id removed.";
} on Exception catch (e) {
ctx.resp.status = 404;
ctx.resp.body = "Profile $id not found. $e";
ctx.res.status = 404;
ctx.res.body = "Profile $id not found. $e";
}

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

ctx.resp.status = 303;
ctx.resp.headers["Location"] = [photoUrl];
ctx.res.status = 303;
ctx.res.headers["Location"] = [photoUrl];

return ctx;
});
Expand Down
1 change: 0 additions & 1 deletion lib/api.dart

This file was deleted.

1 change: 1 addition & 0 deletions lib/nitric.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
library;

export 'src/nitric.dart';
export 'src/context/common.dart';
3 changes: 1 addition & 2 deletions lib/resources.dart
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
export 'src/resources/resources.dart';
export 'src/context/common.dart';
export 'src/resources/common.dart';
69 changes: 13 additions & 56 deletions lib/src/api/bucket.dart
Original file line number Diff line number Diff line change
@@ -1,28 +1,31 @@
import 'dart:async';
import 'dart:convert';

import 'package:nitric_sdk/nitric.dart';
import 'package:nitric_sdk/src/context/common.dart';
import 'package:nitric_sdk/src/grpc_helper.dart';
import 'package:nitric_sdk/src/nitric/proto/storage/v1/storage.pbgrpc.dart'
as $p;
import 'package:nitric_sdk/src/google/protobuf/duration.pb.dart' as $d;
import 'package:fixnum/fixnum.dart';
import 'package:grpc/grpc.dart';
import 'package:nitric_sdk/src/workers/common.dart';

class Bucket {
late $p.StorageClient _storageClient;
late $p.StorageListenerClient? _storageListenerClient;

String name;

Bucket(this.name, {$p.StorageClient? client}) {
Bucket(this.name,
{$p.StorageClient? client,
$p.StorageListenerClient? storageListenerClient}) {
if (client == null) {
final channel = createClientChannelFromEnvVar();

_storageClient = $p.StorageClient(channel);
_storageClient =
$p.StorageClient(ClientChannelSingleton.instance.clientChannel);
} else {
_storageClient = client;
}

_storageListenerClient = storageListenerClient;
}

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

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

var worker = BlobEventWorker(registrationRequest, handler, this);
var worker = FileEventWorker(registrationRequest, handler, this,
client: _storageListenerClient);

worker.start();
await worker.start();
}
}

Expand Down Expand Up @@ -149,50 +153,3 @@ class File {
return resp.url;
}
}

class BlobEventWorker implements Worker {
$p.RegistrationRequest registrationRequest;
BlobEventHandler middleware;
Bucket bucket;

BlobEventWorker(this.registrationRequest, this.middleware, this.bucket);

@override
Future<void> start() async {
// Create Storage listener client
final channel = createClientChannelFromEnvVar();
final client = $p.StorageListenerClient(channel);

final initMsg = $p.ClientMessage(registrationRequest: registrationRequest);

// Create the request stream and send the initial message
final requestStream = StreamController<$p.ClientMessage>();
requestStream.add(initMsg);

final response = client.listen(
requestStream.stream,
);

await for (final msg in response) {
if (msg.hasRegistrationResponse()) {
// Blob Notification has connected with Nitric server
} else if (msg.hasBlobEventRequest()) {
var ctx = BlobEventContext.fromRequest(msg, bucket);

try {
ctx = await middleware(ctx);
} on GrpcError catch (e) {
print("caught a GrpcError: $e");
} catch (e) {
print("unhandled application error: $e");

ctx.resp.success = false;
}

requestStream.add(ctx.toResponse());
}
}

await channel.shutdown();
}
}
5 changes: 2 additions & 3 deletions lib/src/api/keyvalue.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,8 @@ class KeyValueStore {

KeyValueStore(this.name, {$p.KvStoreClient? client}) {
if (client == null) {
var channel = createClientChannelFromEnvVar();

_keyValueClient = $p.KvStoreClient(channel);
_keyValueClient =
$p.KvStoreClient(ClientChannelSingleton.instance.clientChannel);
} else {
_keyValueClient = client;
}
Expand Down
9 changes: 4 additions & 5 deletions lib/src/api/queue.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import 'dart:async';

import 'package:nitric_sdk/api.dart';
import 'package:nitric_sdk/src/api/api.dart';
import 'package:nitric_sdk/src/grpc_helper.dart';
import 'package:nitric_sdk/src/nitric/proto/queues/v1/queues.pbgrpc.dart' as $p;

Expand All @@ -13,9 +13,8 @@ class Queue {
/// Construct a new queue.
Queue(this.name, {$p.QueuesClient? client}) {
if (client == null) {
final channel = createClientChannelFromEnvVar();

_queuesClient = $p.QueuesClient(channel);
_queuesClient =
$p.QueuesClient(ClientChannelSingleton.instance.clientChannel);
} else {
_queuesClient = client;
}
Expand Down Expand Up @@ -61,7 +60,7 @@ class DequeuedMessage {
}

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

Expand Down
5 changes: 2 additions & 3 deletions lib/src/api/secret.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@ class Secret {

Secret(this.name, {$p.SecretManagerClient? client}) {
if (client == null) {
final channel = createClientChannelFromEnvVar();

_secretClient = $p.SecretManagerClient(channel);
_secretClient =
$p.SecretManagerClient(ClientChannelSingleton.instance.clientChannel);
} else {
_secretClient = client;
}
Expand Down
5 changes: 2 additions & 3 deletions lib/src/api/topic.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,8 @@ class Topic {

Topic(this.name, {$p.TopicsClient? client}) {
if (client == null) {
final channel = createClientChannelFromEnvVar();

_topicsClient = $p.TopicsClient(channel);
_topicsClient =
$p.TopicsClient(ClientChannelSingleton.instance.clientChannel);
} else {
_topicsClient = client;
}
Expand Down
Loading
Loading