mirror of
https://github.com/versia-pub/server.git
synced 2025-12-06 08:28:19 +01:00
Compare commits
4 commits
8c0a20a743
...
9eac364e01
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9eac364e01 | ||
|
|
d3f411915f | ||
|
|
7bd07801f2 | ||
|
|
287f428a83 |
|
|
@ -5,6 +5,7 @@
|
||||||
### API
|
### API
|
||||||
|
|
||||||
- [x] 🥺 Emoji Reactions are now available! You can react to any note with custom emojis.
|
- [x] 🥺 Emoji Reactions are now available! You can react to any note with custom emojis.
|
||||||
|
- [x] 🔎 Added support for [batch account data API](https://docs.joinmastodon.org/methods/accounts/#index).
|
||||||
|
|
||||||
# `0.8.0` • Federation 2: Electric Boogaloo
|
# `0.8.0` • Federation 2: Electric Boogaloo
|
||||||
|
|
||||||
|
|
|
||||||
52
api/api/v1/accounts/index.get.test.ts
Normal file
52
api/api/v1/accounts/index.get.test.ts
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
import { afterAll, describe, expect, test } from "bun:test";
|
||||||
|
import { generateClient, getTestUsers } from "~/tests/utils";
|
||||||
|
|
||||||
|
const { users, deleteUsers } = await getTestUsers(5);
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await deleteUsers();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("/api/v1/accounts", () => {
|
||||||
|
test("should return accounts", async () => {
|
||||||
|
await using client = await generateClient();
|
||||||
|
|
||||||
|
const { data, ok } = await client.getAccounts(users.map((u) => u.id));
|
||||||
|
|
||||||
|
expect(ok).toBe(true);
|
||||||
|
expect(data).toEqual(
|
||||||
|
expect.arrayContaining(
|
||||||
|
users.map((u) =>
|
||||||
|
expect.objectContaining({
|
||||||
|
id: u.id,
|
||||||
|
username: u.data.username,
|
||||||
|
acct: u.data.username,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should skip nonexistent accounts", async () => {
|
||||||
|
await using client = await generateClient();
|
||||||
|
|
||||||
|
const { data, ok } = await client.getAccounts([
|
||||||
|
...users.map((u) => u.id),
|
||||||
|
"00000000-0000-0000-0000-000000000000",
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(ok).toBe(true);
|
||||||
|
expect(data).toEqual(
|
||||||
|
expect.arrayContaining(
|
||||||
|
users.map((u) =>
|
||||||
|
expect.objectContaining({
|
||||||
|
id: u.id,
|
||||||
|
username: u.data.username,
|
||||||
|
acct: u.data.username,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
expect(data).toHaveLength(users.length);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { zBoolean } from "@versia/client/schemas";
|
import { Account as AccountSchema, zBoolean } from "@versia/client/schemas";
|
||||||
import { User } from "@versia/kit/db";
|
import { User } from "@versia/kit/db";
|
||||||
import { Users } from "@versia/kit/tables";
|
import { Users } from "@versia/kit/tables";
|
||||||
import { and, eq, isNull } from "drizzle-orm";
|
import { and, eq, isNull } from "drizzle-orm";
|
||||||
|
|
@ -6,7 +6,7 @@ import { describeRoute } from "hono-openapi";
|
||||||
import { resolver, validator } from "hono-openapi/zod";
|
import { resolver, validator } from "hono-openapi/zod";
|
||||||
import ISO6391 from "iso-639-1";
|
import ISO6391 from "iso-639-1";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { apiRoute, auth, handleZodError, jsonOrForm } from "@/api";
|
import { apiRoute, auth, handleZodError, jsonOrForm, qsQuery } from "@/api";
|
||||||
import { tempmailDomains } from "@/tempmail";
|
import { tempmailDomains } from "@/tempmail";
|
||||||
import { ApiError } from "~/classes/errors/api-error";
|
import { ApiError } from "~/classes/errors/api-error";
|
||||||
import { config } from "~/config.ts";
|
import { config } from "~/config.ts";
|
||||||
|
|
@ -42,7 +42,67 @@ const schema = z.object({
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default apiRoute((app) =>
|
export default apiRoute((app) => {
|
||||||
|
app.get(
|
||||||
|
"/api/v1/accounts",
|
||||||
|
describeRoute({
|
||||||
|
summary: "Get multiple accounts",
|
||||||
|
description: "View information about multiple profiles.",
|
||||||
|
externalDocs: {
|
||||||
|
url: "https://docs.joinmastodon.org/methods/accounts/#index",
|
||||||
|
},
|
||||||
|
tags: ["Accounts"],
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
description:
|
||||||
|
"Account records for the requested confirmed and approved accounts. There can be fewer records than requested if the accounts do not exist or are not confirmed.",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: resolver(z.array(AccountSchema)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
422: ApiError.validationFailed().schema,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
qsQuery(),
|
||||||
|
auth({
|
||||||
|
auth: false,
|
||||||
|
scopes: [],
|
||||||
|
challenge: false,
|
||||||
|
}),
|
||||||
|
rateLimit(40),
|
||||||
|
validator(
|
||||||
|
"query",
|
||||||
|
z.object({
|
||||||
|
id: z
|
||||||
|
.array(AccountSchema.shape.id)
|
||||||
|
.min(1)
|
||||||
|
.max(40)
|
||||||
|
.or(AccountSchema.shape.id.transform((v) => [v]))
|
||||||
|
.openapi({
|
||||||
|
description: "The IDs of the Accounts in the database.",
|
||||||
|
example: [
|
||||||
|
"f137ce6f-ff5e-4998-b20f-0361ba9be007",
|
||||||
|
"8424c654-5d03-4a1b-bec8-4e87db811b5d",
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
handleZodError,
|
||||||
|
),
|
||||||
|
async (context) => {
|
||||||
|
const { id: ids } = context.req.valid("query");
|
||||||
|
|
||||||
|
// Find accounts by IDs
|
||||||
|
const accounts = await User.fromIds(ids);
|
||||||
|
|
||||||
|
return context.json(
|
||||||
|
accounts.map((account) => account.toApi()),
|
||||||
|
200,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
app.post(
|
app.post(
|
||||||
"/api/v1/accounts",
|
"/api/v1/accounts",
|
||||||
describeRoute({
|
describeRoute({
|
||||||
|
|
@ -360,5 +420,5 @@ export default apiRoute((app) =>
|
||||||
|
|
||||||
return context.text("", 200);
|
return context.text("", 200);
|
||||||
},
|
},
|
||||||
),
|
);
|
||||||
);
|
});
|
||||||
|
|
|
||||||
|
|
@ -71,9 +71,7 @@ export default apiRoute((app) =>
|
||||||
const { user } = context.get("auth");
|
const { user } = context.get("auth");
|
||||||
|
|
||||||
// TODO: Implement with_suspended
|
// TODO: Implement with_suspended
|
||||||
const { id } = context.req.valid("query");
|
const { id: ids } = context.req.valid("query");
|
||||||
|
|
||||||
const ids = Array.isArray(id) ? id : [id];
|
|
||||||
|
|
||||||
const relationships = await Relationship.fromOwnerAndSubjects(
|
const relationships = await Relationship.fromOwnerAndSubjects(
|
||||||
user,
|
user,
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@ export default apiRoute((app) => {
|
||||||
|
|
||||||
if (timeline.includes("home")) {
|
if (timeline.includes("home")) {
|
||||||
const found = await db.query.Markers.findFirst({
|
const found = await db.query.Markers.findFirst({
|
||||||
where: (marker, { and, eq }): SQL | undefined =>
|
where: (marker): SQL | undefined =>
|
||||||
and(
|
and(
|
||||||
eq(marker.userId, user.id),
|
eq(marker.userId, user.id),
|
||||||
eq(marker.timeline, "home"),
|
eq(marker.timeline, "home"),
|
||||||
|
|
@ -102,7 +102,7 @@ export default apiRoute((app) => {
|
||||||
|
|
||||||
if (timeline.includes("notifications")) {
|
if (timeline.includes("notifications")) {
|
||||||
const found = await db.query.Markers.findFirst({
|
const found = await db.query.Markers.findFirst({
|
||||||
where: (marker, { and, eq }): SQL | undefined =>
|
where: (marker): SQL | undefined =>
|
||||||
and(
|
and(
|
||||||
eq(marker.userId, user.id),
|
eq(marker.userId, user.id),
|
||||||
eq(marker.timeline, "notifications"),
|
eq(marker.timeline, "notifications"),
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { RolePermission, Status as StatusSchema } from "@versia/client/schemas";
|
import { RolePermission, Status as StatusSchema } from "@versia/client/schemas";
|
||||||
import { db } from "@versia/kit/db";
|
import { db } from "@versia/kit/db";
|
||||||
import type { SQL } from "drizzle-orm";
|
import { and, eq, type SQL } from "drizzle-orm";
|
||||||
import { describeRoute } from "hono-openapi";
|
import { describeRoute } from "hono-openapi";
|
||||||
import { resolver } from "hono-openapi/zod";
|
import { resolver } from "hono-openapi/zod";
|
||||||
import { apiRoute, auth, withNoteParam } from "@/api";
|
import { apiRoute, auth, withNoteParam } from "@/api";
|
||||||
|
|
@ -51,7 +51,7 @@ export default apiRoute((app) =>
|
||||||
|
|
||||||
if (
|
if (
|
||||||
await db.query.UserToPinnedNotes.findFirst({
|
await db.query.UserToPinnedNotes.findFirst({
|
||||||
where: (userPinnedNote, { and, eq }): SQL | undefined =>
|
where: (userPinnedNote): SQL | undefined =>
|
||||||
and(
|
and(
|
||||||
eq(userPinnedNote.noteId, note.data.id),
|
eq(userPinnedNote.noteId, note.data.id),
|
||||||
eq(userPinnedNote.userId, user.id),
|
eq(userPinnedNote.userId, user.id),
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,37 @@
|
||||||
import { afterAll, describe, expect, test } from "bun:test";
|
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
||||||
|
import { randomUUIDv7 } from "bun";
|
||||||
|
import { Emoji } from "~/classes/database/emoji";
|
||||||
|
import { Media } from "~/classes/database/media";
|
||||||
import { generateClient, getTestStatuses, getTestUsers } from "~/tests/utils";
|
import { generateClient, getTestStatuses, getTestUsers } from "~/tests/utils";
|
||||||
|
|
||||||
const { users, deleteUsers } = await getTestUsers(3);
|
const { users, deleteUsers } = await getTestUsers(3);
|
||||||
const timeline = (await getTestStatuses(2, users[0])).toReversed();
|
const timeline = (await getTestStatuses(2, users[0])).toReversed();
|
||||||
|
let emojiMedia: Media;
|
||||||
|
let customEmoji: Emoji;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
emojiMedia = await Media.insert({
|
||||||
|
id: randomUUIDv7(),
|
||||||
|
content: {
|
||||||
|
"image/png": {
|
||||||
|
content: "https://example.com/image.png",
|
||||||
|
remote: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
customEmoji = await Emoji.insert({
|
||||||
|
id: randomUUIDv7(),
|
||||||
|
shortcode: "test_emoji",
|
||||||
|
visibleInPicker: true,
|
||||||
|
mediaId: emojiMedia.id,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await deleteUsers();
|
await deleteUsers();
|
||||||
|
await customEmoji.delete();
|
||||||
|
await emojiMedia.delete();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("/api/v1/statuses/:id/reactions/:name", () => {
|
describe("/api/v1/statuses/:id/reactions/:name", () => {
|
||||||
|
|
@ -40,6 +66,29 @@ describe("/api/v1/statuses/:id/reactions/:name", () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("should add custom emoji reaction", async () => {
|
||||||
|
await using client = await generateClient(users[1]);
|
||||||
|
|
||||||
|
const { data, ok } = await client.createEmojiReaction(
|
||||||
|
timeline[0].id,
|
||||||
|
`:${customEmoji.data.shortcode}:`,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(ok).toBe(true);
|
||||||
|
expect(data.reactions).toContainEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
name: `:${customEmoji.data.shortcode}:`,
|
||||||
|
count: 1,
|
||||||
|
me: true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(data.emojis).toContainEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
shortcode: customEmoji.data.shortcode,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test("should add multiple different reactions", async () => {
|
test("should add multiple different reactions", async () => {
|
||||||
await using client1 = await generateClient(users[1]);
|
await using client1 = await generateClient(users[1]);
|
||||||
await using client2 = await generateClient(users[2]);
|
await using client2 = await generateClient(users[2]);
|
||||||
|
|
|
||||||
|
|
@ -105,19 +105,12 @@ export default apiRoute((app) => {
|
||||||
emoji = unicodeEmoji;
|
emoji = unicodeEmoji;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use the User react method
|
|
||||||
try {
|
|
||||||
await user.react(note, emoji);
|
await user.react(note, emoji);
|
||||||
|
|
||||||
// Reload note to get updated reactions
|
// Reload note to get updated reactions
|
||||||
await note.reload(user.id);
|
await note.reload(user.id);
|
||||||
|
|
||||||
return context.json(await note.toApi(user), 201);
|
return context.json(await note.toApi(user), 201);
|
||||||
} catch {
|
|
||||||
// If it's already reacted, just return the current status
|
|
||||||
await note.reload(user.id);
|
|
||||||
return context.json(await note.toApi(user), 201);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -206,7 +199,6 @@ export default apiRoute((app) => {
|
||||||
emoji = unicodeEmoji;
|
emoji = unicodeEmoji;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use the User unreact method
|
|
||||||
await user.unreact(note, emoji);
|
await user.unreact(note, emoji);
|
||||||
|
|
||||||
// Reload note to get updated reactions
|
// Reload note to get updated reactions
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ export default apiRoute((app) => {
|
||||||
const { id } = context.req.valid("param");
|
const { id } = context.req.valid("param");
|
||||||
|
|
||||||
const userFilter = await db.query.Filters.findFirst({
|
const userFilter = await db.query.Filters.findFirst({
|
||||||
where: (filter, { eq, and }): SQL | undefined =>
|
where: (filter): SQL | undefined =>
|
||||||
and(eq(filter.userId, user.id), eq(filter.id, id)),
|
and(eq(filter.userId, user.id), eq(filter.id, id)),
|
||||||
with: {
|
with: {
|
||||||
keywords: true,
|
keywords: true,
|
||||||
|
|
@ -224,7 +224,7 @@ export default apiRoute((app) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedFilter = await db.query.Filters.findFirst({
|
const updatedFilter = await db.query.Filters.findFirst({
|
||||||
where: (filter, { eq, and }): SQL | undefined =>
|
where: (filter): SQL | undefined =>
|
||||||
and(eq(filter.userId, user.id), eq(filter.id, id)),
|
and(eq(filter.userId, user.id), eq(filter.id, id)),
|
||||||
with: {
|
with: {
|
||||||
keywords: true,
|
keywords: true,
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import {
|
||||||
import { db } from "@versia/kit/db";
|
import { db } from "@versia/kit/db";
|
||||||
import { FilterKeywords, Filters } from "@versia/kit/tables";
|
import { FilterKeywords, Filters } from "@versia/kit/tables";
|
||||||
import { randomUUIDv7 } from "bun";
|
import { randomUUIDv7 } from "bun";
|
||||||
import type { SQL } from "drizzle-orm";
|
import { eq, type SQL } from "drizzle-orm";
|
||||||
import { describeRoute } from "hono-openapi";
|
import { describeRoute } from "hono-openapi";
|
||||||
import { resolver, validator } from "hono-openapi/zod";
|
import { resolver, validator } from "hono-openapi/zod";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
@ -44,8 +44,7 @@ export default apiRoute((app) => {
|
||||||
const { user } = context.get("auth");
|
const { user } = context.get("auth");
|
||||||
|
|
||||||
const userFilters = await db.query.Filters.findMany({
|
const userFilters = await db.query.Filters.findMany({
|
||||||
where: (filter, { eq }): SQL | undefined =>
|
where: (filter): SQL | undefined => eq(filter.userId, user.id),
|
||||||
eq(filter.userId, user.id),
|
|
||||||
with: {
|
with: {
|
||||||
keywords: true,
|
keywords: true,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
2
bun.lock
2
bun.lock
|
|
@ -86,7 +86,7 @@
|
||||||
},
|
},
|
||||||
"packages/client": {
|
"packages/client": {
|
||||||
"name": "@versia/client",
|
"name": "@versia/client",
|
||||||
"version": "0.2.0-alpha.1",
|
"version": "0.2.0-alpha.3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@badgateway/oauth2-client": "^3.0.0",
|
"@badgateway/oauth2-client": "^3.0.0",
|
||||||
"iso-639-1": "^3.1.5",
|
"iso-639-1": "^3.1.5",
|
||||||
|
|
|
||||||
|
|
@ -261,7 +261,7 @@ export class Note extends BaseInterface<typeof Notes, NoteTypeWithRelations> {
|
||||||
{
|
{
|
||||||
with: {
|
with: {
|
||||||
relationships: {
|
relationships: {
|
||||||
where: (relationship, { eq, and }): SQL | undefined =>
|
where: (relationship): SQL | undefined =>
|
||||||
and(
|
and(
|
||||||
eq(relationship.subjectId, this.data.authorId),
|
eq(relationship.subjectId, this.data.authorId),
|
||||||
eq(relationship.following, true),
|
eq(relationship.following, true),
|
||||||
|
|
@ -597,7 +597,7 @@ export class Note extends BaseInterface<typeof Notes, NoteTypeWithRelations> {
|
||||||
if (this.data.visibility === "private") {
|
if (this.data.visibility === "private") {
|
||||||
return user
|
return user
|
||||||
? !!(await db.query.Relationships.findFirst({
|
? !!(await db.query.Relationships.findFirst({
|
||||||
where: (relationship, { and, eq }): SQL | undefined =>
|
where: (relationship): SQL | undefined =>
|
||||||
and(
|
and(
|
||||||
eq(relationship.ownerId, user?.id),
|
eq(relationship.ownerId, user?.id),
|
||||||
eq(relationship.subjectId, Notes.authorId),
|
eq(relationship.subjectId, Notes.authorId),
|
||||||
|
|
@ -641,6 +641,18 @@ export class Note extends BaseInterface<typeof Notes, NoteTypeWithRelations> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const reactions = this.getReactions(userFetching ?? undefined).map(
|
||||||
|
// Remove account_ids
|
||||||
|
(r) => ({
|
||||||
|
...r,
|
||||||
|
account_ids: undefined,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const emojis = data.emojis.concat(
|
||||||
|
data.reactions.map((r) => r.emoji).filter((v) => v !== null),
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: data.id,
|
id: data.id,
|
||||||
in_reply_to_id: data.replyId || null,
|
in_reply_to_id: data.replyId || null,
|
||||||
|
|
@ -652,7 +664,7 @@ export class Note extends BaseInterface<typeof Notes, NoteTypeWithRelations> {
|
||||||
: undefined,
|
: undefined,
|
||||||
card: null,
|
card: null,
|
||||||
content: replacedContent,
|
content: replacedContent,
|
||||||
emojis: data.emojis.map((emoji) => new Emoji(emoji).toApi()),
|
emojis: emojis.map((emoji) => new Emoji(emoji).toApi()),
|
||||||
favourited: data.liked,
|
favourited: data.liked,
|
||||||
favourites_count: data.likeCount,
|
favourites_count: data.likeCount,
|
||||||
media_attachments: (data.attachments ?? []).map((a) =>
|
media_attachments: (data.attachments ?? []).map((a) =>
|
||||||
|
|
@ -699,13 +711,7 @@ export class Note extends BaseInterface<typeof Notes, NoteTypeWithRelations> {
|
||||||
edited_at: data.updatedAt
|
edited_at: data.updatedAt
|
||||||
? new Date(data.updatedAt).toISOString()
|
? new Date(data.updatedAt).toISOString()
|
||||||
: null,
|
: null,
|
||||||
reactions: this.getReactions(userFetching ?? undefined).map(
|
reactions,
|
||||||
// Remove account_ids
|
|
||||||
(r) => ({
|
|
||||||
...r,
|
|
||||||
account_ids: undefined,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
text: data.contentSource,
|
text: data.contentSource,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -204,7 +204,7 @@ export class Relationship extends BaseInterface<
|
||||||
ownerId: string;
|
ownerId: string;
|
||||||
}): Promise<RelationshipType> {
|
}): Promise<RelationshipType> {
|
||||||
let output = await db.query.Relationships.findFirst({
|
let output = await db.query.Relationships.findFirst({
|
||||||
where: (rel, { and, eq }): SQL | undefined =>
|
where: (rel): SQL | undefined =>
|
||||||
and(
|
and(
|
||||||
eq(rel.ownerId, oppositeTo.subjectId),
|
eq(rel.ownerId, oppositeTo.subjectId),
|
||||||
eq(rel.subjectId, oppositeTo.ownerId),
|
eq(rel.subjectId, oppositeTo.ownerId),
|
||||||
|
|
|
||||||
|
|
@ -91,8 +91,7 @@ export class Role extends BaseInterface<typeof Roles> {
|
||||||
): Promise<Role[]> {
|
): Promise<Role[]> {
|
||||||
return (
|
return (
|
||||||
await db.query.RoleToUsers.findMany({
|
await db.query.RoleToUsers.findMany({
|
||||||
where: (role, { eq }): SQL | undefined =>
|
where: (role): SQL | undefined => eq(role.userId, userId),
|
||||||
eq(role.userId, userId),
|
|
||||||
with: {
|
with: {
|
||||||
role: true,
|
role: true,
|
||||||
user: {
|
user: {
|
||||||
|
|
|
||||||
|
|
@ -464,7 +464,7 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
|
||||||
> {
|
> {
|
||||||
// Get all linked accounts
|
// Get all linked accounts
|
||||||
const accounts = await db.query.OpenIdAccounts.findMany({
|
const accounts = await db.query.OpenIdAccounts.findMany({
|
||||||
where: (User, { eq }): SQL | undefined => eq(User.userId, this.id),
|
where: (User): SQL | undefined => eq(User.userId, this.id),
|
||||||
});
|
});
|
||||||
|
|
||||||
return accounts
|
return accounts
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://jsr.io/schema/config-file.v1.json",
|
"$schema": "https://jsr.io/schema/config-file.v1.json",
|
||||||
"name": "@versia/client",
|
"name": "@versia/client",
|
||||||
"version": "0.2.0-alpha.2",
|
"version": "0.2.0-alpha.3",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./index.ts",
|
".": "./index.ts",
|
||||||
"./schemas": "./schemas.ts"
|
"./schemas": "./schemas.ts"
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@versia/client",
|
"name": "@versia/client",
|
||||||
"displayName": "Versia Client",
|
"displayName": "Versia Client",
|
||||||
"version": "0.2.0-alpha.2",
|
"version": "0.2.0-alpha.3",
|
||||||
"author": {
|
"author": {
|
||||||
"email": "jesse.wierzbinski@lysand.org",
|
"email": "jesse.wierzbinski@lysand.org",
|
||||||
"name": "Jesse Wierzbinski (CPlusPatch)",
|
"name": "Jesse Wierzbinski (CPlusPatch)",
|
||||||
|
|
|
||||||
|
|
@ -703,6 +703,28 @@ export class Client extends BaseClient {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /api/v1/accounts
|
||||||
|
*
|
||||||
|
* @param ids The account IDs.
|
||||||
|
* @return An array of accounts.
|
||||||
|
*/
|
||||||
|
public getAccounts(
|
||||||
|
ids: string[],
|
||||||
|
extra?: RequestInit,
|
||||||
|
): Promise<Output<z.infer<typeof Account>[]>> {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
|
||||||
|
for (const id of ids) {
|
||||||
|
params.append("id[]", id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.get<z.infer<typeof Account>[]>(
|
||||||
|
`/api/v1/accounts?${params.toString()}`,
|
||||||
|
extra,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GET /api/v1/accounts/id
|
* GET /api/v1/accounts/id
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -156,7 +156,7 @@ export default (plugin: PluginType): void => {
|
||||||
|
|
||||||
// Check if account is already linked
|
// Check if account is already linked
|
||||||
const account = await db.query.OpenIdAccounts.findFirst({
|
const account = await db.query.OpenIdAccounts.findFirst({
|
||||||
where: (account, { eq, and }): SQL | undefined =>
|
where: (account): SQL | undefined =>
|
||||||
and(
|
and(
|
||||||
eq(account.serverId, sub),
|
eq(account.serverId, sub),
|
||||||
eq(account.issuerId, issuer.id),
|
eq(account.issuerId, issuer.id),
|
||||||
|
|
@ -195,7 +195,7 @@ export default (plugin: PluginType): void => {
|
||||||
|
|
||||||
let userId = (
|
let userId = (
|
||||||
await db.query.OpenIdAccounts.findFirst({
|
await db.query.OpenIdAccounts.findFirst({
|
||||||
where: (account, { eq, and }): SQL | undefined =>
|
where: (account): SQL | undefined =>
|
||||||
and(
|
and(
|
||||||
eq(account.serverId, sub),
|
eq(account.serverId, sub),
|
||||||
eq(account.issuerId, issuer.id),
|
eq(account.issuerId, issuer.id),
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { RolePermission } from "@versia/client/schemas";
|
import { RolePermission } from "@versia/client/schemas";
|
||||||
import { db } from "@versia/kit/db";
|
import { db } from "@versia/kit/db";
|
||||||
import { eq, type SQL } from "@versia/kit/drizzle";
|
import { and, eq, type SQL } from "@versia/kit/drizzle";
|
||||||
import { OpenIdAccounts } from "@versia/kit/tables";
|
import { OpenIdAccounts } from "@versia/kit/tables";
|
||||||
import { describeRoute } from "hono-openapi";
|
import { describeRoute } from "hono-openapi";
|
||||||
import { resolver, validator } from "hono-openapi/zod";
|
import { resolver, validator } from "hono-openapi/zod";
|
||||||
|
|
@ -58,7 +58,7 @@ export default (plugin: PluginType): void => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const account = await db.query.OpenIdAccounts.findFirst({
|
const account = await db.query.OpenIdAccounts.findFirst({
|
||||||
where: (account, { eq, and }): SQL | undefined =>
|
where: (account): SQL | undefined =>
|
||||||
and(
|
and(
|
||||||
eq(account.userId, user.id),
|
eq(account.userId, user.id),
|
||||||
eq(account.issuerId, issuerId),
|
eq(account.issuerId, issuerId),
|
||||||
|
|
@ -127,7 +127,7 @@ export default (plugin: PluginType): void => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const account = await db.query.OpenIdAccounts.findFirst({
|
const account = await db.query.OpenIdAccounts.findFirst({
|
||||||
where: (account, { eq, and }): SQL | undefined =>
|
where: (account): SQL | undefined =>
|
||||||
and(
|
and(
|
||||||
eq(account.userId, user.id),
|
eq(account.userId, user.id),
|
||||||
eq(account.issuerId, issuerId),
|
eq(account.issuerId, issuerId),
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { type Application, db } from "@versia/kit/db";
|
import { type Application, db } from "@versia/kit/db";
|
||||||
import type { InferSelectModel, SQL } from "@versia/kit/drizzle";
|
import { eq, type InferSelectModel, type SQL } from "@versia/kit/drizzle";
|
||||||
import type { OpenIdLoginFlows } from "@versia/kit/tables";
|
import type { OpenIdLoginFlows } from "@versia/kit/tables";
|
||||||
import {
|
import {
|
||||||
type AuthorizationResponseError,
|
type AuthorizationResponseError,
|
||||||
|
|
@ -39,7 +39,7 @@ const getFlow = (
|
||||||
| undefined
|
| undefined
|
||||||
> => {
|
> => {
|
||||||
return db.query.OpenIdLoginFlows.findFirst({
|
return db.query.OpenIdLoginFlows.findFirst({
|
||||||
where: (flow, { eq }): SQL | undefined => eq(flow.id, flowId),
|
where: (flow): SQL | undefined => eq(flow.id, flowId),
|
||||||
with: {
|
with: {
|
||||||
application: true,
|
application: true,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@
|
||||||
//"isolatedDeclarations": true,
|
//"isolatedDeclarations": true,
|
||||||
"verbatimModuleSyntax": true,
|
"verbatimModuleSyntax": true,
|
||||||
"moduleDetection": "force",
|
"moduleDetection": "force",
|
||||||
"target": "esnext",
|
"target": "ESNext",
|
||||||
"jsx": "preserve",
|
"jsx": "preserve",
|
||||||
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|
||||||
"composite": true,
|
"composite": true,
|
||||||
|
|
|
||||||
|
|
@ -170,7 +170,7 @@ export const checkRouteNeedsChallenge = async (
|
||||||
}
|
}
|
||||||
|
|
||||||
const challenge = await db.query.Challenges.findFirst({
|
const challenge = await db.query.Challenges.findFirst({
|
||||||
where: (c, { eq }): SQL | undefined => eq(c.id, challenge_id),
|
where: (c): SQL | undefined => eq(c.id, challenge_id),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!challenge) {
|
if (!challenge) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue