-
-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #428 from fastrodev/dev
feat: add user module
- Loading branch information
Showing
7 changed files
with
236 additions
and
1 deletion.
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,123 @@ | ||
import { assertEquals } from "@app/http/server/deps.ts"; | ||
import { | ||
createUser, | ||
deleteUser, | ||
getUser, | ||
getUserByEmail, | ||
listUsers, | ||
listUsersByEmail, | ||
updateUser, | ||
} from "@app/modules/user/user.service.ts"; | ||
import UserType from "@app/modules/user/user.type.ts"; | ||
import { collectValues, kv } from "@app/utils/db.ts"; | ||
|
||
Deno.test({ | ||
name: "createUser", | ||
async fn() { | ||
const res = await createUser({ | ||
username: "john", | ||
password: "password", | ||
email: "[email protected]", | ||
}); | ||
await createUser({ | ||
username: "john1", | ||
password: "password", | ||
email: "[email protected]", | ||
}); | ||
await createUser({ | ||
username: "john3", | ||
password: "password", | ||
email: "[email protected]", | ||
}); | ||
await createUser({ | ||
username: "john4", | ||
password: "password", | ||
email: "[email protected]", | ||
}); | ||
assertEquals(res.ok, true); | ||
}, | ||
}); | ||
|
||
let user: UserType | null; | ||
Deno.test({ | ||
name: "getUserByEmail", | ||
async fn() { | ||
user = await getUserByEmail("[email protected]"); | ||
assertEquals(user?.email, "[email protected]"); | ||
}, | ||
}); | ||
|
||
Deno.test({ | ||
name: "updateUser", | ||
async fn() { | ||
if (!user) return; | ||
user.email = "[email protected]"; | ||
if (user.id) { | ||
const res = await updateUser(user?.id, user); | ||
assertEquals(res?.ok, true); | ||
} | ||
}, | ||
}); | ||
|
||
Deno.test({ | ||
name: "getUser", | ||
async fn() { | ||
if (user?.id) { | ||
user = await getUser(user?.id); | ||
} | ||
assertEquals(user?.email, "[email protected]"); | ||
}, | ||
}); | ||
|
||
Deno.test({ | ||
name: "listUsers", | ||
async fn() { | ||
const res = await collectValues(listUsers()); | ||
assertEquals(res.length, 4); | ||
}, | ||
}); | ||
|
||
Deno.test({ | ||
name: "listUsers", | ||
async fn() { | ||
const iterator = listUsers({ limit: 1 }); | ||
const res = await collectValues(iterator); | ||
assertEquals(res.length, 1); | ||
|
||
const iter2 = listUsers({ limit: 1, cursor: iterator.cursor }); | ||
const res2 = await collectValues(iter2); | ||
assertEquals(res2.length, 1); | ||
|
||
const iter3 = listUsers({ limit: 1, cursor: iterator.cursor }); | ||
const res3 = await collectValues(iter3); | ||
assertEquals(res3.length, 1); | ||
}, | ||
}); | ||
|
||
Deno.test({ | ||
name: "listUsersByEmail", | ||
async fn() { | ||
const res = await collectValues(listUsersByEmail()); | ||
assertEquals(res.length, 4); | ||
}, | ||
}); | ||
|
||
Deno.test({ | ||
name: "deleteUser", | ||
async fn() { | ||
if (user?.id) { | ||
const res = await deleteUser(user.id); | ||
assertEquals(res?.ok, true); | ||
} | ||
}, | ||
}); | ||
|
||
Deno.test({ | ||
name: "reset", | ||
async fn() { | ||
const iter = kv.list({ prefix: [] }); | ||
const promises = []; | ||
for await (const res of iter) promises.push(kv.delete(res.key)); | ||
await Promise.all(promises); | ||
}, | ||
}); |
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,77 @@ | ||
import { ulid } from "jsr:@std/ulid"; | ||
import { kv } from "@app/utils/db.ts"; | ||
import UserType from "@app/modules/user/user.type.ts"; | ||
|
||
export async function getUserByEmail(email: string) { | ||
const res = await kv.get<UserType>(["users_by_email", email]); | ||
return res.value; | ||
} | ||
|
||
export async function getUser(id: string): Promise<UserType | null> { | ||
const res = await kv.get<UserType>(["users", id]); | ||
return res.value; | ||
} | ||
|
||
export function listUsers( | ||
options?: Deno.KvListOptions, | ||
) { | ||
return kv.list<UserType>({ prefix: ["users"] }, options); | ||
} | ||
|
||
export function listUsersByEmail( | ||
options?: Deno.KvListOptions, | ||
) { | ||
return kv.list<UserType>({ prefix: ["users_by_email"] }, options); | ||
} | ||
|
||
export async function createUser(user: UserType) { | ||
user.id = user.id ? user.id : ulid(); | ||
const primaryKey = ["users", user.id]; | ||
const byEmailKey = ["users_by_email", user.email]; | ||
|
||
const res = await kv.atomic() | ||
.check({ key: primaryKey, versionstamp: null }) | ||
.check({ key: byEmailKey, versionstamp: null }) | ||
.set(primaryKey, user) | ||
.set(byEmailKey, user) | ||
.commit(); | ||
|
||
if (!res.ok) { | ||
throw new TypeError("User with ID or email already exists"); | ||
} | ||
|
||
return res; | ||
} | ||
|
||
export async function updateUser(id: string, user: UserType) { | ||
if (!id) return; | ||
const existingUser = await kv.get<UserType>(["users", id]); | ||
if (!existingUser.value?.email) return; | ||
|
||
const byEmailKey = ["users_by_email", user.email]; | ||
const atomicOp = kv.atomic() | ||
.check(existingUser) | ||
.delete(["users_by_email", existingUser.value?.email]) | ||
.set(["users", id], user) | ||
.check({ key: byEmailKey, versionstamp: null }) | ||
.set(byEmailKey, user); | ||
|
||
const res = await atomicOp.commit(); | ||
if (!res.ok) throw new Error("Failed to update user"); | ||
return res; | ||
} | ||
|
||
export async function deleteUser(id: string) { | ||
let res = { ok: false }; | ||
while (!res.ok) { | ||
const getRes = await kv.get<UserType>(["users", id]); | ||
if (getRes && getRes.value) { | ||
res = await kv.atomic() | ||
.check(getRes) | ||
.delete(["users", id]) | ||
.delete(["users_by_email", getRes.value.email]) | ||
.commit(); | ||
} | ||
} | ||
return res; | ||
} |
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,10 @@ | ||
type UserType = { | ||
id?: string; | ||
username: string; | ||
email: string; | ||
password: string; | ||
group?: string[]; | ||
image?: string; | ||
}; | ||
|
||
export default UserType; |
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,13 @@ | ||
import { kv } from "@app/utils/db.ts"; | ||
|
||
function replacer(_key: unknown, value: unknown) { | ||
return typeof value === "bigint" ? value.toString() : value; | ||
} | ||
|
||
const items = await Array.fromAsync( | ||
kv.list({ prefix: [] }), | ||
({ key, value }) => ({ key, value }), | ||
); | ||
console.log(JSON.stringify(items, replacer, 2)); | ||
|
||
kv.close(); |
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,8 @@ | ||
import { kv } from "@app/utils/db.ts"; | ||
if (!confirm("WARNING: The database will be reset. Continue?")) Deno.exit(); | ||
const iter = kv.list({ prefix: [] }); | ||
const promises = []; | ||
for await (const res of iter) promises.push(kv.delete(res.key)); | ||
await Promise.all(promises); | ||
|
||
kv.close(); |
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