[go: up one dir, main page]

Skip to content

Commit

Permalink
automatic resource id - not working
Browse files Browse the repository at this point in the history
  • Loading branch information
nstfkc committed Oct 16, 2024
1 parent a48f1e5 commit 3d7bd62
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 136 deletions.
134 changes: 68 additions & 66 deletions packages/gemi/http/ApiRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,10 @@ export type ApiRoutes = Record<
| FileHandler
| RouteHandlers
| typeof ApiRouter
| ((resourceId: string) => ResourceRoutes<any, string>)
>;

type ResourceRoutes<
export type ResourceRoutes<
T extends new () => ResourceController,
U extends PropertyKey,
> = Record<
Expand All @@ -113,6 +114,7 @@ type ResourceRoutes<
>;

export class ApiRouter {
static __brand = "ApiRouter";
public routes: ApiRoutes = {};
public middlewares: string[] = [];
public middleware(_req: HttpRequest<any, any>): MiddlewareReturnType {}
Expand Down Expand Up @@ -187,43 +189,20 @@ export class ApiRouter {
return new RouteHandler("DELETE", handler, methodName);
}

public resource<
T extends new () => ResourceController,
U extends PropertyKey,
>(Controller: T, id: U) {
class ResourceRouter extends ApiRouter {
routes = {
public resource<T extends new () => ResourceController>(Controller: T) {
return <U extends PropertyKey>(resourceId: U) => {
return {
"/": {
list: this.get(
Controller,
"list" as ControllerMethods<T>,
) as ParseRouteHandler<T, TestControllerMethod<T, "list">, "GET">,
create: this.post(
Controller,
"create" as ControllerMethods<T>,
) as ParseRouteHandler<T, TestControllerMethod<T, "create">, "POST">,
list: new RouteHandler("GET", Controller, "list"),
create: new RouteHandler("POST", Controller, "create"),
},
[`/:${String(id)}`]: {
show: this.get(
Controller,
"show" as ControllerMethods<T>,
) as ParseRouteHandler<T, TestControllerMethod<T, "show">, "GET">,
update: this.put(
Controller,
"update" as ControllerMethods<T>,
) as ParseRouteHandler<T, TestControllerMethod<T, "update">, "PUT">,
delete: this.delete(
Controller,
"delete" as ControllerMethods<T>,
) as ParseRouteHandler<
T,
TestControllerMethod<T, "delete">,
"DELETE"
>,
[`/:${String(resourceId)}`]: {
show: new RouteHandler("GET", Controller, "show"),
update: new RouteHandler("PUT", Controller, "update"),
delete: new RouteHandler("DELETE", Controller, "delete"),
},
} as ResourceRoutes<T, U>;
}
return ResourceRouter;
};
}

public file<Input, Output, Params>(
Expand Down Expand Up @@ -290,53 +269,76 @@ type ParsePrefixAndKey<
? `${T1}/${T2}`
: U;

type LastUrlSegment<T extends string> =
T extends `${infer _}/${infer LastSegment}` ? LastUrlSegment<LastSegment> : T;

type ResourceRoutesParser<
T extends (resourceId: string) => ResourceRoutes<any, string>,
U extends PropertyKey,
R = ReturnType<T>,
> = {
[K in keyof R]: R[K] extends RouteHandlers
? K extends "/"
? RouteHandlersParser<R[K], `${U & string}`>
: RouteHandlersParser<
R[K],
`${U & string}/:${LastUrlSegment<U & string>}Id`
>
: never;
}[keyof R];

type RouteParser<
T extends ApiRoutes,
Prefix extends PropertyKey = "",
K extends keyof T = keyof T,
> = K extends any
? T[K] extends RouteHandler<any, any, any, any>
? RouteHandlerParser<T[K], ParsePrefixAndKey<Prefix, K>>
: T[K] extends new () => ApiRouter
? RouterInstanceParser<T[K], ParsePrefixAndKey<Prefix, K>>
: T[K] extends RouteHandlers
? RouteHandlersParser<
T[K],
`${Prefix & string}${K extends "/" ? "" : K & string}`
>
: never
: T[K] extends (resourceId: string) => ResourceRoutes<any, string>
? ResourceRoutesParser<T[K], ParsePrefixAndKey<Prefix, K>>
: T[K] extends new () => ApiRouter
? RouterInstanceParser<T[K], ParsePrefixAndKey<Prefix, K>>
: T[K] extends RouteHandlers
? RouteHandlersParser<
T[K],
`${Prefix & string}${K extends "/" ? "" : K & string}`
>
: never
: never;

export type CreateRPC<
T extends ApiRouter,
Prefix extends PropertyKey = "",
> = KeyAndValueToObject<RouteParser<T["routes"], Prefix>>;

// type Test<T> = IdKey<T> extends PropertyKey ? { [key: IdKey<T>]: true } : {};

// function createIdKey<T>(id: T): IdKey<T> {
// return `/:${id}` as IdKey<T>;
// export class FooController extends ResourceController {
// async index() {
// return {};
// }
// details(req: HttpRequest) {
// return {};
// }

// list(req: HttpRequest) {
// return [
// { id: "1", uuid: req.params.id },
// { id: "2", uuid: req.params.id },
// ];
// }
// show(req: HttpRequest) {
// return { id: "ENES" };
// }
// create() {}
// update(req: HttpRequest) {
// return req.params;
// }
// delete() {}
// }

// function readKey<T extends PropertyKey>(key: T): Test<T> {
// return {
// "/": true,
// [createIdKey(key)]: false,
// } as any as Test<T>;
// class XX extends ApiRouter {
// routes = {
// "/foo": this.resource(FooController),
// };
// }

// const x = readKey("y");

// x["/:y"];

// {
// "/": {
// list: ParseRouteHandler<T, TestControllerMethod<T, "list">, "GET">;
// create: ParseRouteHandler<T, TestControllerMethod<T, "create">, "POST">;
// };
// "/:id": {
// show: ParseRouteHandler<T, TestControllerMethod<T, "show">, "GET">;
// update: ParseRouteHandler<T, TestControllerMethod<T, "update">, "PUT">;
// delete: ParseRouteHandler<T, TestControllerMethod<T, "delete">, "DELETE">;
// };
// }
// type X = CreateRPC<XX>;
2 changes: 1 addition & 1 deletion packages/gemi/internal/isConstructor.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export function isConstructor(value: any) {
return typeof value === "function" && value.prototype !== undefined;
return typeof value === "function" && typeof value.constructor === "function";
}
27 changes: 17 additions & 10 deletions packages/gemi/services/router/createFlatApiRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@ import {
type ApiRouter,
} from "../../http/ApiRouter";
import type { RouterMiddleware } from "../../http/Router";
import { isConstructor } from "../../internal/isConstructor";

type ApiRouteExec = any;

function isRouter(
routeHandlers: RouteHandlers | FileHandler | (new () => ApiRouter),
): routeHandlers is new () => ApiRouter {
return isConstructor(routeHandlers);
if ("__brand" in routeHandlers) {
return routeHandlers.__brand === "ApiRouter";
}
return false;
}

export type FlatApiRoutes = Record<
Expand Down Expand Up @@ -42,10 +44,19 @@ export function createFlatApiRoutes(routes: ApiRoutes) {
exec,
middleware,
};
} else if (isRouter(apiRouteHandlerOrApiRouter)) {
const router = new apiRouteHandlerOrApiRouter();
} else if (typeof apiRouteHandlerOrApiRouter === "function") {
let routes: ApiRoutes = {};
let routerMiddlewares = [];
if (isRouter(apiRouteHandlerOrApiRouter)) {
const router = new apiRouteHandlerOrApiRouter();
routes = router.routes;
routerMiddlewares = [router.middleware, ...router.middlewares];
} else {
const [lastSegment] = rootPath.split("/").reverse();
routes = apiRouteHandlerOrApiRouter(`${lastSegment}Id`);
}

const result = createFlatApiRoutes(router.routes);
const result = createFlatApiRoutes(routes);
for (const [path, handlers] of Object.entries(result)) {
const subPath = path === "/" ? "" : path;
const _rootPath = rootPath === "/" ? "" : rootPath;
Expand All @@ -57,11 +68,7 @@ export function createFlatApiRoutes(routes: ApiRoutes) {
for (const [method, handler] of Object.entries(handlers)) {
flatApiRoutes[finalPath][method] = {
exec: handler.exec,
middleware: [
router.middleware,
...router.middlewares,
...handler.middleware,
],
middleware: [...routerMiddlewares, ...handler.middleware],
};
}
}
Expand Down
70 changes: 13 additions & 57 deletions templates/saas-starter/app/http/router/api.ts
Original file line number Diff line number Diff line change
@@ -1,65 +1,21 @@
import { prisma } from "@/app/database/prisma";
import { ApiRouter, HttpRequest, ResourceController } from "gemi/http";
import {
ApiRouter,
HttpRequest,
ResourceController,
type CreateRPC,
} from "gemi/http";
import { Auth, Broadcast, FileStorage } from "gemi/facades";
import { WelcomeEmail } from "@/app/email/WelcomeEmail";
import { FooController } from "../controllers/FooController";

export default class extends ApiRouter {
class A extends ApiRouter {
routes = {
"/users": this.get(async () => {
return await prisma.user.findFirst();
}),
"/home": this.get(async (req: HttpRequest<{ color: string }>) => {
const input = req.search;
const items = [
{ id: 1, name: "Red", hex: "#FF0000", color: "red" },
{ id: 2, name: "Green", hex: "#00FF00", color: "green" },
{ id: 3, name: "Blue", hex: "#0000FF", color: "blue" },
{ id: 4, name: "Yellow", hex: "#FFFF00", color: "yellow" },
{ id: 5, name: "Purple", hex: "#800080", color: "purple" },
];
const filteredItems = items.filter((item) =>
input.get("color")
? input.get("color").split(".").includes(item.color)
: true,
);

return filteredItems;
}),

"/test/:testId": this.get((req: HttpRequest) => {
return `${req.params.testId}`;
}),

"/email": this.get(async () => {
const result = await WelcomeEmail.send({ data: { name: "Enes" } });
return { success: result };
}),

"/upload": this.post(async (req: HttpRequest<{ images: Blob[] }>) => {
const input = await req.input();
console.log(input.get("images"));
return {};
}),
"/foo": this.resource(FooController, "fooId"),
"/file/:path*": this.get(async (req: HttpRequest<{ path: string }>) => {
return await FileStorage.fetch(req.params.path);
}),
"/invite": this.get(async () => {
return await prisma.organizationInvitation.create({
data: {
email: "enesxtufekci+2@gmail.com",
organizationId: 1,
role: 0,
},
});
}),
"/organization": this.get(async () => {
return await prisma.organization.create({
data: {
name: "Enes org",
},
});
}),
"/test": this.post(FooController, "index"),
"/foo": this.resource(FooController),
};
}

type X = CreateRPC<A>;

export default A;
4 changes: 2 additions & 2 deletions templates/saas-starter/app/views/FooEdit.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Form, usePut, useQuery } from "gemi/client";
import { Form, useMutation, usePut, useQuery } from "gemi/client";

export default function FooEdit() {
const { data } = useQuery("/foo/:fooId");
const { data } = useQuery("");
console.log({ data });
return (
<div>
Expand Down
2 changes: 2 additions & 0 deletions templates/saas-starter/rpc.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import type { CreateRPC } from "gemi/http";
import type { CreateViewRPC } from "gemi/http";
import { type Dictionary } from "./app/i18n";

type X = CreateRPC<Api>;

declare module "gemi/client" {
export interface RPC extends CreateRPC<Api> {}
export interface ViewRPC extends CreateViewRPC<View> {}
Expand Down

0 comments on commit 3d7bd62

Please sign in to comment.