-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add an endpoint to retrieve user information
- Loading branch information
Showing
12 changed files
with
218 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import { responseBuilder } from "utils/response"; | ||
|
||
export const UNAUTHORIZED = responseBuilder("error", { | ||
error: "Unauthorized", | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
# Plugins | ||
|
||
All plugins are located in this directory. | ||
In Elysia-JS a plugin is middleware. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import { bearer } from "@elysiajs/bearer"; | ||
import Elysia from "elysia"; | ||
import { verifyToken } from "utils/auth/jwt"; | ||
|
||
type IsAuthorized = { | ||
isAuthorized: true; | ||
username: string; | ||
createdBy: "login" | "refresh"; | ||
}; | ||
type IsNotAuthorized = { | ||
isAuthorized: false; | ||
}; | ||
|
||
type Auth = IsAuthorized | IsNotAuthorized; | ||
|
||
/** | ||
* Middleware to check if the user is authorized | ||
*/ | ||
export const auth = new Elysia({ | ||
name: "auth-plugin", | ||
}) | ||
.use(bearer()) | ||
.derive({ as: "global" }, ({ bearer }) => ({ | ||
get auth(): Auth { | ||
const unauthorizedRes = { isAuthorized: false } as const; | ||
|
||
if (!bearer) return unauthorizedRes; | ||
|
||
const { payload, isValid } = verifyToken(bearer); | ||
if (!isValid) return unauthorizedRes; | ||
if (!(payload.type === "access")) return unauthorizedRes; | ||
|
||
return { | ||
isAuthorized: true, | ||
username: payload.username, | ||
createdBy: payload.createdBy, | ||
}; | ||
}, | ||
})); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import e from "@edgedb"; | ||
import Elysia, { t } from "elysia"; | ||
import { HttpStatusCode } from "elysia-http-status-code"; | ||
import { client } from "index"; | ||
import { auth } from "plugins/auth"; | ||
import { promiseResult } from "utils/errors"; | ||
import { replaceDateWithTimestamp } from "utils/objects/transform"; | ||
import { responseBuilder } from "utils/response"; | ||
import { userOwnInfoRouter } from "./me"; | ||
|
||
export const userInfoRouter = new Elysia({ prefix: "/info" }) | ||
.use(auth) | ||
.use(HttpStatusCode()) | ||
.use(userOwnInfoRouter) | ||
.get( | ||
"/", | ||
async ({ auth, query, set, httpStatus }) => { | ||
const { username } = query; | ||
// The creation date is only returned when the user is requesting information about themselves | ||
const isThemself = auth.isAuthorized && username === auth.username; | ||
|
||
const dbQuery = e.select(e.User, (u) => ({ | ||
username: true, | ||
displayname: true, | ||
created: isThemself, | ||
filter_single: e.op(u.username, "=", username), | ||
})); | ||
|
||
const result = await promiseResult(async () => dbQuery.run(client)); | ||
|
||
if (result.status !== "success") { | ||
set.status = httpStatus.HTTP_500_INTERNAL_SERVER_ERROR; | ||
return responseBuilder("error", { | ||
error: "Internal server error", | ||
}); | ||
} | ||
|
||
if (!result.data) { | ||
set.status = httpStatus.HTTP_404_NOT_FOUND; | ||
return responseBuilder("error", { | ||
error: "User not found", | ||
}); | ||
} | ||
|
||
return responseBuilder("success", { | ||
data: replaceDateWithTimestamp(result.data), | ||
message: "User information retrieved successfully", | ||
}); | ||
}, | ||
{ | ||
query: t.Object({ | ||
username: t.String({ | ||
minLength: 1, | ||
description: "The username of the user to get information about", | ||
}), | ||
}), | ||
detail: { | ||
description: | ||
"Get information about a user. When authirzing and requesting oneself the creation date will also be received", | ||
tags: ["User"], | ||
}, | ||
}, | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { UNAUTHORIZED } from "constants/responses"; | ||
import Elysia from "elysia"; | ||
import { HttpStatusCode } from "elysia-http-status-code"; | ||
import { auth } from "plugins/auth"; | ||
|
||
export const userOwnInfoRouter = new Elysia({ prefix: "/me" }) | ||
.use(auth) | ||
.use(HttpStatusCode()) | ||
.get("/", ({ auth, set, httpStatus }) => { | ||
if (!auth.isAuthorized) { | ||
set.status = httpStatus.HTTP_401_UNAUTHORIZED; | ||
return UNAUTHORIZED; | ||
} | ||
|
||
// An authorized request to that endpoint will give a lot of info | ||
set.redirect = `/user/info?username=${auth.username}`; | ||
}, { | ||
detail: { | ||
description: "Get information about yourself. For this the user needs to authenthicated", | ||
tags: ["User"], | ||
}, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
// TODO: Write a unit test | ||
|
||
/** | ||
* Executes a provided callback which returns a Promise and handles the result. | ||
* @template T - The type of data returned by the Promise. | ||
* @param callback - The callback function that returns a Promise. | ||
* @returns A Promise that resolves to an object with status "success" and the data returned by the callback, or an object with status "error" if the Promise is rejected. | ||
*/ | ||
export const promiseResult = async <T>(callback: () => Promise<T>) => { | ||
return callback() | ||
.then( | ||
(data) => | ||
({ | ||
status: "success", | ||
data, | ||
}) as const, | ||
) | ||
.catch( | ||
() => | ||
({ | ||
status: "error", | ||
}) as const, | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
type Object = Record<string | number | symbol, unknown>; | ||
|
||
type ConvertDatesOther<T extends Object, R> = { | ||
[K in keyof T]: T[K] extends Date ? R : T[K]; | ||
}; | ||
|
||
type DateCallback<T> = (val: Date) => T; | ||
|
||
export const replaceDate = <T extends Object, R>( | ||
obj: T, | ||
callback: DateCallback<R>, | ||
): ConvertDatesOther<T, R> => { | ||
const pairs = Object.entries(obj); | ||
const newPairs: [string, unknown][] = []; | ||
for (const [key, val] of pairs) { | ||
if (val instanceof Date) newPairs.push([key, callback(val)]); | ||
else newPairs.push([key, val]); | ||
} | ||
|
||
return Object.fromEntries(newPairs) as ConvertDatesOther<T, R>; | ||
}; | ||
|
||
/** | ||
* Replaces all Date objects in an object with their timestamp | ||
*/ | ||
export const replaceDateWithTimestamp = <T extends Object>(obj: T) => | ||
replaceDate(obj, (val) => val.getTime()); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import { describe, expect, it } from "bun:test"; | ||
import { replaceDateWithTimestamp } from "utils/objects/transform"; | ||
|
||
describe("transform", () => { | ||
it("replaces all dates with timestamps", () => { | ||
const obj = { | ||
date: new Date("2021-01-01"), | ||
number: 1, | ||
string: "hello", | ||
}; | ||
|
||
const result = replaceDateWithTimestamp(obj); | ||
expect(result).toEqual({ | ||
date: 1609459200000, | ||
number: 1, | ||
string: "hello", | ||
}); | ||
}); | ||
}); |