Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • car-app/appwrite-functions
1 result
Show changes
Commits on Source (10)
Showing
with 619 additions and 4 deletions
include:
- local: test-vars.gitlab-ci.yml
- local: prod-vars.gitlab-ci.yml
rules:
- if: $PROD == "true"
variables:
PROJECT_ID: 65fcb48c714ed68ec324
stages:
- deploy
deploy_api:
stage: deploy
image: registry.420joos.dev/ubuntu:22.04
script:
- apt update && apt install -y curl
- curl -sL https://raw.githubusercontent.com/appwrite/sdk-for-cli/5.0.1/install.sh | bash
- appwrite client --endpoint ${APPWRITE_URL} --projectId ${PROJECT_ID} --key ${API_KEY}
- appwrite deploy function --all
{
"projectId": "65fcb48c714ed68ec324",
"projectName": "",
"functions": [
{
"$id": "65fde2f5abbbe63b7804",
"name": "api",
"runtime": "dart-3.3",
"execute": [
"users"
],
"events": [],
"schedule": "",
"timeout": 15,
"enabled": true,
"logging": true,
"entrypoint": "lib/main.dart",
"commands": "dart pub get",
"ignore": [
".packages",
".dart_tool"
],
"path": "functions/api"
}
]
}
\ No newline at end of file
# See https://www.dartlang.org/guides/libraries/private-files
# Files and directories created by pub
.dart_tool/
.packages
build/
# If you're building an application, you may want to check-in your pubspec.lock
pubspec.lock
# Directory created by dartdoc
# If you don't generate documentation locally you can remove this line.
doc/api/
# dotenv environment variables file
.env*
# Avoid committing generated Javascript files:
*.dart.js
*.info.json # Produced by the --dump-info flag.
*.js # When generated by dart2js. Don't specify *.js if your
# project includes source files written in JavaScript.
*.js_
*.js.deps
*.js.map
.flutter-plugins
.flutter-plugins-dependencies
\ No newline at end of file
# Point
## 🧰 Usage
### GET /
- Returns a "Hello, World!" message.
**Response**
Sample `200` Response:
```text
Hello, World!
```
### POST, PUT, PATCH, DELETE /
- Returns a "Learn More" JSON response.
**Response**
Sample `200` Response:
```json
{
"motto": "Build like a team of hundreds_",
"learn": "https://appwrite.io/docs",
"connect": "https://appwrite.io/discord",
"getInspired": "https://builtwith.appwrite.io"
}
```
## ⚙️ Configuration
| Setting | Value |
|-------------------|-----------------|
| Runtime | Dart (3.1) |
| Entrypoint | `lib/main.dart` |
| Build Commands | `dart pub get` |
| Permissions | `any` |
| Timeout (Seconds) | 15 |
## 🔒 Environment Variables
No environment variables required.
include: package:lints/recommended.yaml
\ No newline at end of file
../../../lib/
\ No newline at end of file
import 'dart:async';
import 'dart:io';
import 'package:point/membership/membership_api_endpoint.dart';
import 'package:point/membership/membership_service.dart';
import 'package:point/point/point_api_endpoint.dart';
import 'package:lib/lib.dart';
import 'package:point/route/route_api_endpoint.dart';
import 'package:point/schedule/schedule_api_endpoint.dart';
Future<dynamic> main(final context) async {
final host = "https://${context.req.host}/v1";
final apiKey = Platform.environment['FUNCTION_API_KEY'] ?? "";
final projectId = AppwriteVariables.projectId;
final String requestPath = context.req.path;
ApiEndpoint apiEndpoint;
switch (requestPath) {
case ScheduleApiEndpoint.path:
apiEndpoint = ScheduleApiEndpoint(trigger: AppwriteVariables.trigger(context));
break;
case PointApiEndpoint.path:
var membershipService = getMembershipService(context, host, projectId, apiKey);
var requestingUser = await getRequestingUser(context, membershipService);
apiEndpoint = PointApiEndpoint(requestingUser: requestingUser, host: host, projectId: projectId, apiKey: apiKey);
break;
case RouteApiEndpoint.path:
var membershipService = getMembershipService(context, host, projectId, apiKey);
var requestingUser = await getRequestingUser(context, membershipService);
apiEndpoint = RouteApiEndpoint(requestingUser: requestingUser, host: host, projectId: projectId, apiKey: apiKey);
break;
case MembershipApiEndpoint.path:
var membershipService = getMembershipService(context, host, projectId, apiKey);
apiEndpoint = MembershipApiEndpoint(membershipService: membershipService);
default:
throw HttpException("Path $requestPath not found", uri: Uri.parse(context.req.url));
}
var requestHandler = RequestHandler(apiEndpoint: apiEndpoint);
return requestHandler.handleRequest(context);
}
MembershipService getMembershipService(final context, String host, String projectId, String apiKey) {
final userId = AppwriteVariables.userId(context);
return MembershipService(host: host, projectId: projectId, apiKey: apiKey, userId: userId);
}
Future<UserDetails> getRequestingUser(final context, MembershipService membershipService) async {
final userId = AppwriteVariables.userId(context);
return UserDetails(
userId: userId,
teamIds: (await membershipService.getMemberships().toList()).map((e) => e.teamId).toList(),
);
}
import 'dart:convert';
import 'package:lib/lib.dart';
import 'package:point/membership/membership_service.dart';
class MembershipApiEndpoint extends ApiEndpoint {
static const String path = "/membership";
MembershipService _membershipService;
MembershipApiEndpoint({required MembershipService membershipService}) :
_membershipService = membershipService;
@override
Future<Map<String, dynamic>> handleDelete(context) {
throw ApiActionForbiddenException("delete", "membership");
}
@override
Future<Map<String, dynamic>> handleGet(context) async {
var memberships = await _membershipService.getMemberships().toList();
var response = memberships.map((e) => e.toResponse()).toList();
return context.res.send(jsonEncode(response));
}
@override
Future<Map<String, dynamic>> handlePost(context) {
throw ApiActionForbiddenException("post", "membership");
}
@override
Future<Map<String, dynamic>> handlePut(context) {
throw ApiActionForbiddenException("put", "membership");
}
}
import 'package:dart_appwrite/dart_appwrite.dart';
import 'package:lib/lib.dart';
import 'package:point/membership/team.dart';
class MembershipService extends ClientFunction {
String _userId;
MembershipService({required super.host, required super.projectId, required super.apiKey, required String userId}) :
_userId = userId;
Stream<Team> getMemberships() async* {
var users = Users(client);
var membershipList = await users.listMemberships(userId: _userId);
for (var membership in membershipList.memberships) {
if(membership.confirm) {
yield Team(teamId: membership.teamId, teamName: membership.teamName);
}
}
}
}
class Team {
static const String teamIdResponseFieldName = "teamId";
static const String teamNameResponseFieldName = "teamName";
final String _teamId;
final String _teamName;
String get teamId => _teamId;
String get teamName => _teamName;
Team({required String teamId, required String teamName}) :
_teamId = teamId,
_teamName = teamName;
Map<String, dynamic> toResponse() {
return {
teamIdResponseFieldName: teamId,
teamNameResponseFieldName: teamName,
};
}
}
import 'package:lib/lib.dart';
class Point implements DbModel {
static const String latitudeDbFieldName = "latitude";
static const String longitudeDbFieldName = "longitude";
static const String latitudeRequestResponseFieldName = "latitude";
static const String longitudeRequestResponseFieldName = "longitude";
final String? _id;
final num _latitude;
final num _longitude;
@override
String? get id => _id;
@override
Permissions? get permissions => null;
num get latitude => _latitude;
num get longitude => _longitude;
Point({String? id, required num latitude, required num longitude}) :
_id = id,
_latitude = latitude,
_longitude = longitude;
@override
Map<String, dynamic> toDbData() {
return {
latitudeDbFieldName: latitude,
longitudeDbFieldName: longitude,
};
}
@override
Map<String, dynamic> toResponseData() {
return {
RequestResponseVariables.idFieldName: id,
latitudeRequestResponseFieldName: latitude,
longitudeRequestResponseFieldName: longitude,
};
}
factory Point.fromRequest(Map<String, dynamic> map) {
return Point(
id: getOptionalMapItem(RequestResponseVariables.idFieldName, map),
latitude: getRequiredMapItem(latitudeRequestResponseFieldName, map),
longitude: getRequiredMapItem(longitudeRequestResponseFieldName, map),
);
}
factory Point.fromDbData(Map<String, dynamic> map){
return Point(
id: getRequiredMapItem(DatabaseVariables.idColumnName, map),
latitude: getRequiredMapItem(latitudeDbFieldName, map),
longitude: getRequiredMapItem(longitudeDbFieldName, map),
);
}
}
import 'package:lib/lib.dart';
import 'package:point/point/point.dart';
import 'package:point/point/point_database.dart';
class PointApiEndpoint extends DbApiEndpoint {
static const String path = "/point";
PointApiEndpoint({required super.requestingUser, required String host, required String projectId, required String apiKey}) :
super(
database: PointDatabase(host: host, projectId: projectId, apiKey: apiKey),
itemGenerator: Point.fromRequest
);
}
import 'package:dart_appwrite/dart_appwrite.dart';
import 'package:lib/lib.dart';
import 'package:point/point/point.dart';
class PointDatabase extends BaseDatabase<Point> {
@override
String get databaseId => "65fddef523b653ce0952";
@override
String get collectionId => "65fddefc7f63cb43b15b";
PointDatabase({required super.host, required super.projectId, required super.apiKey}) :
super(itemFromDbMap: Point.fromDbData);
@override
Future<DbResponse<Point>> create(DbRequest<Point> request) async {
var queries = List.of([
Query.equal(Point.latitudeDbFieldName, request.item.latitude),
Query.equal(Point.longitudeDbFieldName, request.item.longitude),
]);
var equalItems = await getFromQueries(DbRequest(item: queries, requestingUser: request.requestingUser)).toList();
if (equalItems.isNotEmpty) {
return equalItems.first;
}
return super.create(request);
}
@override
Future<DbResponse<Point>> update(DbRequest<Point> request) {
throw DbActionForbiddenException("update", "point");
}
@override
Future<DbResponse<Point>> delete(DbRequest<String> request) {
throw DbActionForbiddenException("delete", "point"); // Maybe deletion is possible
}
@override
Stream<DbResponse<Point>> getAll(DbRequest request) {
throw DbActionForbiddenException("get all", "point");
}
}
import 'package:lib/lib.dart';
import 'package:point/point/point.dart';
class Route implements DbModel {
static const String userIdDbFieldName = "userId";
static const String teamIdDbFieldName = "teamId";
static const String pointsDbFieldName = "points";
static const String userIdRequestResponseFieldName = "userId";
static const String teamIdRequestResponseFieldName = "teamId";
static const String pointsRequestResponseFieldName = "points";
final String? _id;
final List<Point> _points;
final String _userId;
final String? _teamId;
@override
String? get id => _id;
@override
Permissions? get permissions => null;
Iterable<Point> get points => _points;
String get userId => _userId;
String? get teamId => _teamId;
Route({String? id, required List<Point> points, required String userId, String? teamId}) :
_id = id,
_points = points,
_userId = userId,
_teamId = teamId;
@override
Map<String, dynamic> toDbData() {
var pointIds = points.map((e) => e.id).toList(growable: false);
return {
pointsDbFieldName: pointIds,
userIdDbFieldName : userId,
teamIdDbFieldName : teamId,
};
}
@override
Map<String, dynamic> toResponseData() {
return {
RequestResponseVariables.idFieldName: id,
pointsRequestResponseFieldName : points.map((e) => e.toResponseData()).toList(growable: false),
userIdRequestResponseFieldName: userId,
teamIdRequestResponseFieldName: teamId,
};
}
factory Route.fromRequest(Map<String, dynamic> map, String userId) {
return Route(
id: getOptionalMapItem(RequestResponseVariables.idFieldName, map),
points: getRequiredMapItem<Iterable>(pointsRequestResponseFieldName, map).map((e) => Point.fromRequest(e)).toList(growable: false),
userId: userId,
teamId: getOptionalMapItem(teamIdRequestResponseFieldName, map),
);
}
factory Route.fromDbData(Map<String, dynamic> map) {
return Route(
id: getOptionalMapItem(DatabaseVariables.idColumnName, map),
points: getRequiredMapItem<Iterable>(pointsDbFieldName, map).map((e) => Point.fromDbData(e)).toList(growable: false),
userId: getRequiredMapItem(userIdDbFieldName, map),
teamId: getOptionalMapItem(teamIdDbFieldName, map),
);
}
}
import 'dart:convert';
import 'package:lib/lib.dart';
import 'package:point/route/route.dart';
import 'package:point/route/route_database.dart';
class RouteApiEndpoint extends DbApiEndpoint<Route> {
static const String path = "/route";
RouteApiEndpoint({required super.requestingUser, required String host, required String projectId, required String apiKey}) :
super(
database: RouteDatabase(host: host, projectId: projectId, apiKey: apiKey),
itemGenerator: (m) => Route.fromRequest(m, requestingUser.userId),
);
@override
Future<Map<String, dynamic>> handleGet(context) async {
if(context.req.body != "") {
return super.handleGet(context);
}
else {
List<Map<String, dynamic>> response = [];
await for (var dbresponse in database.getAll(DbRequest(item: null, requestingUser: requestingUser))) {
response.add(dbresponse.item.toResponseData());
}
return context.res.send(jsonEncode(response)); // context.res.json doesn't support list
}
}
}
import 'package:lib/lib.dart';
import 'package:point/point/point.dart';
import 'package:point/point/point_database.dart';
import 'package:point/route/route.dart';
class RouteDatabase extends BaseDatabase<Route> {
final PointDatabase _pointDatabase;
@override
String get collectionId => "6600a7f577154a949b4c";
@override
String get databaseId => "65fddef523b653ce0952";
RouteDatabase({required super.host, required super.projectId, required super.apiKey}) :
_pointDatabase = PointDatabase(host: host, projectId: projectId, apiKey: apiKey),
super(itemFromDbMap: Route.fromDbData);
@override
Future<DbResponse<Route>> create(DbRequest<Route> request) async {
if (!request.requestingUser.teamIds.contains(request.item.teamId)){
throw UnauthorizedException();
}
var points = <Point>[];
for (var point in request.item.points) {
// must be awaited instead of Future.wait to ensure order
points.add((await _pointDatabase.create(DbRequest(item: point, requestingUser: request.requestingUser))).item);
}
request = DbRequest(
item: Route(
id: request.item.id,
points: points,
userId: request.item.userId,
teamId: request.item.userId
),
requestingUser: request.requestingUser,
);
return super.create(request);
}
@override
Future<DbResponse<Route>> get(DbRequest<String> request) async {
var response = await super.get(request);
if(request.requestingUser.userId != response.item.userId) {
throw UnauthorizedException();
}
return response;
}
@override
Stream<DbResponse<Route>> getFromQueries(DbRequest<Iterable<String>> request) async* {
await for(var queryResult in super.getFromQueries(request)) {
if (request.requestingUser.userId == queryResult.item.userId) {
yield queryResult;
}
}
}
@override
Future<DbResponse<Route>> update(DbRequest<Route> request) async {
var routeId = request.item.id;
if (routeId == null) {
throw RequiredArgumentMissing(RequestResponseVariables.idFieldName);
}
var routeResponse = await get(DbRequest(item: routeId, requestingUser: request.requestingUser));
if (request.requestingUser.userId != routeResponse.item.userId) {
throw UnauthorizedException();
}
return super.update(request);
}
@override
Future<DbResponse<Route>> delete(DbRequest<String> request) async {
var routeResponse = await get(request);
if (request.requestingUser.userId != routeResponse.item.userId) {
throw UnauthorizedException();
}
return super.delete(request);
}
}
import 'package:lib/lib.dart';
class ScheduleApiEndpoint extends ApiEndpoint {
static const String path = "/";
final String _trigger;
ScheduleApiEndpoint({required String trigger}) :
_trigger = trigger;
@override
Future<Map<String, dynamic>> handleDelete(context) {
throw ApiActionForbiddenException("DELETE", path);
}
@override
Future<Map<String, dynamic>> handleGet(context) {
throw ApiActionForbiddenException("GET", path);
}
@override
Future<Map<String, dynamic>> handlePost(context) async {
if(_trigger == "schedule") {
return context.res.send(DateTime.now().toIso8601String());
}
throw ApiActionForbiddenException("POST", path);
}
@override
Future<Map<String, dynamic>> handlePut(context) {
throw ApiActionForbiddenException("PUT", path);
}
}
\ No newline at end of file
name: point
version: 1.0.0
environment:
sdk: ^3.3.0
publish_to: none
dependencies:
dart_appwrite: ^11.0.1
lib:
path: dependencies/lib
dev_dependencies:
lints: ^2.0.0
......@@ -4,9 +4,19 @@
library;
export 'src/interface/client_function.dart';
export 'src/database_function.dart';
export 'src/interface/model.dart';
export 'src/base_database.dart';
export 'src/utils/variables.dart';
export 'src/db_model.dart';
export 'src/utils/db_api_handler.dart';
export 'src/utils/request_handler.dart';
export 'src/exceptions/api_action_forbidden.dart';
export 'src/utils/map_helper.dart';
export 'src/interface/db_model.dart';
export 'src/request_response/db_request.dart';
export 'src/request_response/db_response.dart';
export 'src/utils/permission.dart';
export 'src/utils/permissions.dart';
export 'src/user_details.dart';
export 'src/exceptions/unauthorized.dart';
export 'src/exceptions/required_argument_missing.dart';
export 'src/interface/api_endpoint.dart';
export 'src/db_api_endpoint.dart';
export 'src/exceptions/db_action_forbidden.dart';