oapi: users progress

This commit is contained in:
Puyodead1 2023-03-25 16:09:04 -04:00
parent c97ce59a0a
commit 1ce7879ee8
No known key found for this signature in database
GPG Key ID: A4FA4FEC0DD353FC
27 changed files with 573 additions and 267 deletions

Binary file not shown.

Binary file not shown.

View File

@ -145,6 +145,7 @@ function apiRoutes() {
if (route.description) obj.description = route.description; if (route.description) obj.description = route.description;
if (route.summary) obj.summary = route.summary; if (route.summary) obj.summary = route.summary;
if (route.deprecated) obj.deprecated = route.deprecated;
if (route.requestBody) { if (route.requestBody) {
obj.requestBody = { obj.requestBody = {

View File

@ -144,7 +144,7 @@ router.get(
permission: "VIEW_CHANNEL", permission: "VIEW_CHANNEL",
responses: { responses: {
200: { 200: {
body: "UserPublic", body: "PublicUser",
}, },
400: { 400: {
body: "APIErrorResponse", body: "APIErrorResponse",

View File

@ -30,7 +30,18 @@ const router = Router();
router.post( router.post(
"/", "/",
route({ right: "MANAGE_USERS" }), route({
right: "MANAGE_USERS",
responses: {
204: {},
403: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
await User.findOneOrFail({ await User.findOneOrFail({
where: { id: req.params.id }, where: { id: req.params.id },

View File

@ -16,16 +16,26 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Router, Request, Response } from "express";
import { User } from "@spacebar/util";
import { route } from "@spacebar/api"; import { route } from "@spacebar/api";
import { User } from "@spacebar/util";
import { Request, Response, Router } from "express";
const router: Router = Router(); const router: Router = Router();
router.get("/", route({}), async (req: Request, res: Response) => { router.get(
"/",
route({
responses: {
200: {
body: "PublicUserResponse",
},
},
}),
async (req: Request, res: Response) => {
const { id } = req.params; const { id } = req.params;
res.json(await User.getPublicUser(id)); res.json(await User.getPublicUser(id));
}); },
);
export default router; export default router;

View File

@ -24,7 +24,14 @@ const router: Router = Router();
router.get( router.get(
"/", "/",
route({ responses: { 200: { body: "UserRelationsResponse" } } }), route({
responses: {
200: { body: "UserRelationsResponse" },
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const mutual_relations: object[] = []; const mutual_relations: object[] = [];
const requested_relations = await User.findOneOrFail({ const requested_relations = await User.findOneOrFail({

View File

@ -27,21 +27,40 @@ import { Request, Response, Router } from "express";
const router: Router = Router(); const router: Router = Router();
router.get("/", route({}), async (req: Request, res: Response) => { router.get(
"/",
route({
responses: {
200: {
body: "UserChannelsResponse",
},
},
}),
async (req: Request, res: Response) => {
const recipients = await Recipient.find({ const recipients = await Recipient.find({
where: { user_id: req.user_id, closed: false }, where: { user_id: req.user_id, closed: false },
relations: ["channel", "channel.recipients"], relations: ["channel", "channel.recipients"],
}); });
res.json( res.json(
await Promise.all( await Promise.all(
recipients.map((r) => DmChannelDTO.from(r.channel, [req.user_id])), recipients.map((r) =>
DmChannelDTO.from(r.channel, [req.user_id]),
),
), ),
); );
}); },
);
router.post( router.post(
"/", "/",
route({ requestBody: "DmChannelCreateSchema" }), route({
requestBody: "DmChannelCreateSchema",
responses: {
200: {
body: "DmChannelDTO",
},
},
}),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const body = req.body as DmChannelCreateSchema; const body = req.body as DmChannelCreateSchema;
res.json( res.json(

View File

@ -16,15 +16,28 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Router, Request, Response } from "express";
import { Member, User } from "@spacebar/util";
import { route } from "@spacebar/api"; import { route } from "@spacebar/api";
import { Member, User } from "@spacebar/util";
import bcrypt from "bcrypt"; import bcrypt from "bcrypt";
import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server"; import { HTTPError } from "lambert-server";
const router = Router(); const router = Router();
router.post("/", route({}), async (req: Request, res: Response) => { router.post(
"/",
route({
responses: {
204: {},
401: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const user = await User.findOneOrFail({ const user = await User.findOneOrFail({
where: { id: req.user_id }, where: { id: req.user_id },
select: ["data"], select: ["data"],
@ -33,7 +46,10 @@ router.post("/", route({}), async (req: Request, res: Response) => {
if (user.data.hash) { if (user.data.hash) {
// guest accounts can delete accounts without password // guest accounts can delete accounts without password
correctpass = await bcrypt.compare(req.body.password, user.data.hash); correctpass = await bcrypt.compare(
req.body.password,
user.data.hash,
);
if (!correctpass) { if (!correctpass) {
throw new HTTPError(req.t("auth:login.INVALID_PASSWORD")); throw new HTTPError(req.t("auth:login.INVALID_PASSWORD"));
} }
@ -51,6 +67,7 @@ router.post("/", route({}), async (req: Request, res: Response) => {
} else { } else {
res.sendStatus(401); res.sendStatus(401);
} }
}); },
);
export default router; export default router;

View File

@ -16,14 +16,27 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { User } from "@spacebar/util";
import { Router, Response, Request } from "express";
import { route } from "@spacebar/api"; import { route } from "@spacebar/api";
import { User } from "@spacebar/util";
import bcrypt from "bcrypt"; import bcrypt from "bcrypt";
import { Request, Response, Router } from "express";
const router = Router(); const router = Router();
router.post("/", route({}), async (req: Request, res: Response) => { router.post(
"/",
route({
responses: {
204: {},
400: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const user = await User.findOneOrFail({ const user = await User.findOneOrFail({
where: { id: req.user_id }, where: { id: req.user_id },
select: ["data"], select: ["data"],
@ -32,7 +45,10 @@ router.post("/", route({}), async (req: Request, res: Response) => {
if (user.data.hash) { if (user.data.hash) {
// guest accounts can delete accounts without password // guest accounts can delete accounts without password
correctpass = await bcrypt.compare(req.body.password, user.data.hash); //Not sure if user typed right password :/ correctpass = await bcrypt.compare(
req.body.password,
user.data.hash,
); //Not sure if user typed right password :/
} }
if (correctpass) { if (correctpass) {
@ -45,6 +61,7 @@ router.post("/", route({}), async (req: Request, res: Response) => {
code: 50018, code: 50018,
}); });
} }
}); },
);
export default router; export default router;

View File

@ -16,22 +16,31 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Router, Request, Response } from "express"; import { route } from "@spacebar/api";
import { import {
Config,
Guild, Guild,
Member,
User,
GuildDeleteEvent, GuildDeleteEvent,
GuildMemberRemoveEvent, GuildMemberRemoveEvent,
Member,
User,
emitEvent, emitEvent,
Config,
} from "@spacebar/util"; } from "@spacebar/util";
import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server"; import { HTTPError } from "lambert-server";
import { route } from "@spacebar/api";
const router: Router = Router(); const router: Router = Router();
router.get("/", route({}), async (req: Request, res: Response) => { router.get(
"/",
route({
responses: {
200: {
body: "UserGuildsResponse",
},
},
}),
async (req: Request, res: Response) => {
const members = await Member.find({ const members = await Member.find({
relations: ["guild"], relations: ["guild"],
where: { id: req.user_id }, where: { id: req.user_id },
@ -44,10 +53,24 @@ router.get("/", route({}), async (req: Request, res: Response) => {
} }
res.json(guild); res.json(guild);
}); },
);
// user send to leave a certain guild // user send to leave a certain guild
router.delete("/:guild_id", route({}), async (req: Request, res: Response) => { router.delete(
"/:guild_id",
route({
responses: {
204: {},
400: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { autoJoin } = Config.get().guild; const { autoJoin } = Config.get().guild;
const { guild_id } = req.params; const { guild_id } = req.params;
const guild = await Guild.findOneOrFail({ const guild = await Guild.findOneOrFail({
@ -63,7 +86,10 @@ router.delete("/:guild_id", route({}), async (req: Request, res: Response) => {
autoJoin.guilds.includes(guild_id) && autoJoin.guilds.includes(guild_id) &&
!autoJoin.canLeave !autoJoin.canLeave
) { ) {
throw new HTTPError("You can't leave instance auto join guilds", 400); throw new HTTPError(
"You can't leave instance auto join guilds",
400,
);
} }
await Promise.all([ await Promise.all([
@ -89,6 +115,7 @@ router.delete("/:guild_id", route({}), async (req: Request, res: Response) => {
} as GuildMemberRemoveEvent); } as GuildMemberRemoveEvent);
return res.sendStatus(204); return res.sendStatus(204);
}); },
);
export default router; export default router;

View File

@ -34,18 +34,41 @@ import { Request, Response, Router } from "express";
const router: Router = Router(); const router: Router = Router();
router.get("/", route({}), async (req: Request, res: Response) => { router.get(
"/",
route({
responses: {
200: {
body: "PrivateUserResponse",
},
},
}),
async (req: Request, res: Response) => {
res.json( res.json(
await User.findOne({ await User.findOne({
select: PrivateUserProjection, select: PrivateUserProjection,
where: { id: req.user_id }, where: { id: req.user_id },
}), }),
); );
}); },
);
router.patch( router.patch(
"/", "/",
route({ requestBody: "UserModifySchema" }), route({
requestBody: "UserModifySchema",
responses: {
200: {
body: "UserUpdateResponse",
},
400: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const body = req.body as UserModifySchema; const body = req.body as UserModifySchema;

View File

@ -30,7 +30,20 @@ const router = Router();
router.post( router.post(
"/", "/",
route({ requestBody: "CodesVerificationSchema" }), route({
requestBody: "CodesVerificationSchema",
responses: {
200: {
body: "UserBackupCodesResponse",
},
400: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
// const { key, nonce, regenerate } = req.body as CodesVerificationSchema; // const { key, nonce, regenerate } = req.body as CodesVerificationSchema;
const { regenerate } = req.body as CodesVerificationSchema; const { regenerate } = req.body as CodesVerificationSchema;

View File

@ -33,7 +33,23 @@ const router = Router();
router.post( router.post(
"/", "/",
route({ requestBody: "MfaCodesSchema" }), route({
requestBody: "MfaCodesSchema",
deprecated: true,
description:
"This route is replaced with users/@me/mfa/codes-verification in newer clients",
responses: {
200: {
body: "UserBackupCodesResponse",
},
400: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const { password, regenerate } = req.body as MfaCodesSchema; const { password, regenerate } = req.body as MfaCodesSchema;

View File

@ -16,13 +16,25 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Request, Response, Router } from "express";
import { route } from "@spacebar/api"; import { route } from "@spacebar/api";
import { User, Note, emitEvent, Snowflake } from "@spacebar/util"; import { Note, Snowflake, User, emitEvent } from "@spacebar/util";
import { Request, Response, Router } from "express";
const router: Router = Router(); const router: Router = Router();
router.get("/:id", route({}), async (req: Request, res: Response) => { router.get(
"/:id",
route({
responses: {
200: {
body: "UserNoteResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { id } = req.params; const { id } = req.params;
const note = await Note.findOneOrFail({ const note = await Note.findOneOrFail({
@ -37,9 +49,21 @@ router.get("/:id", route({}), async (req: Request, res: Response) => {
note_user_id: id, note_user_id: id,
user_id: req.user_id, user_id: req.user_id,
}); });
}); },
);
router.put("/:id", route({}), async (req: Request, res: Response) => { router.put(
"/:id",
route({
requestBody: "UserNoteUpdateSchema",
responses: {
204: {},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { id } = req.params; const { id } = req.params;
const owner = await User.findOneOrFail({ where: { id: req.user_id } }); const owner = await User.findOneOrFail({ where: { id: req.user_id } });
const target = await User.findOneOrFail({ where: { id: id } }); //if noted user does not exist throw const target = await User.findOneOrFail({ where: { id: id } }); //if noted user does not exist throw
@ -49,7 +73,10 @@ router.put("/:id", route({}), async (req: Request, res: Response) => {
// upsert a note // upsert a note
if ( if (
await Note.findOne({ await Note.findOne({
where: { owner: { id: owner.id }, target: { id: target.id } }, where: {
owner: { id: owner.id },
target: { id: target.id },
},
}) })
) { ) {
Note.update( Note.update(
@ -81,6 +108,7 @@ router.put("/:id", route({}), async (req: Request, res: Response) => {
}); });
return res.status(204); return res.status(204);
}); },
);
export default router; export default router;

View File

@ -38,7 +38,19 @@ const userProjection: (keyof User)[] = [
...PublicUserProjection, ...PublicUserProjection,
]; ];
router.get("/", route({}), async (req: Request, res: Response) => { router.get(
"/",
route({
responses: {
200: {
body: "UserRelationshipsResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const user = await User.findOneOrFail({ const user = await User.findOneOrFail({
where: { id: req.user_id }, where: { id: req.user_id },
relations: ["relationships", "relationships.to"], relations: ["relationships", "relationships.to"],
@ -56,11 +68,23 @@ router.get("/", route({}), async (req: Request, res: Response) => {
}); });
return res.json(related_users); return res.json(related_users);
}); },
);
router.put( router.put(
"/:id", "/:id",
route({ requestBody: "RelationshipPutSchema" }), route({
requestBody: "RelationshipPutSchema",
responses: {
204: {},
400: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
return await updateRelationship( return await updateRelationship(
req, req,
@ -77,7 +101,18 @@ router.put(
router.post( router.post(
"/", "/",
route({ requestBody: "RelationshipPostSchema" }), route({
requestBody: "RelationshipPostSchema",
responses: {
204: {},
400: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
return await updateRelationship( return await updateRelationship(
req, req,
@ -98,7 +133,20 @@ router.post(
}, },
); );
router.delete("/:id", route({}), async (req: Request, res: Response) => { router.delete(
"/:id",
route({
responses: {
204: {},
400: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { id } = req.params; const { id } = req.params;
if (id === req.user_id) if (id === req.user_id)
throw new HTTPError("You can't remove yourself as a friend"); throw new HTTPError("You can't remove yourself as a friend");
@ -155,7 +203,8 @@ router.delete("/:id", route({}), async (req: Request, res: Response) => {
]); ]);
return res.sendStatus(204); return res.sendStatus(204);
}); },
);
export default router; export default router;

View File

@ -22,17 +22,43 @@ import { Request, Response, Router } from "express";
const router = Router(); const router = Router();
router.get("/", route({}), async (req: Request, res: Response) => { router.get(
"/",
route({
responses: {
200: {
body: "UserSettings",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const user = await User.findOneOrFail({ const user = await User.findOneOrFail({
where: { id: req.user_id }, where: { id: req.user_id },
relations: ["settings"], relations: ["settings"],
}); });
return res.json(user.settings); return res.json(user.settings);
}); },
);
router.patch( router.patch(
"/", "/",
route({ requestBody: "UserSettingsSchema" }), route({
requestBody: "UserSettingsSchema",
responses: {
200: {
body: "UserSettings",
},
400: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const body = req.body as UserSettingsSchema; const body = req.body as UserSettingsSchema;
if (body.locale === "en") body.locale = "en-US"; // fix discord client crash on unkown locale if (body.locale === "en") body.locale = "en-US"; // fix discord client crash on unkown locale

View File

@ -70,6 +70,7 @@ export interface RouteOptions {
values?: string[]; values?: string[];
}; };
}; };
deprecated?: boolean;
// test?: { // test?: {
// response?: RouteResponse; // response?: RouteResponse;
// body?: unknown; // body?: unknown;

View File

@ -86,8 +86,7 @@ export const PrivateUserProjection = [
// Private user data that should never get sent to the client // Private user data that should never get sent to the client
export type PublicUser = Pick<User, PublicUserKeys>; export type PublicUser = Pick<User, PublicUserKeys>;
export type PrivateUser = Pick<User, PrivateUserKeys>;
export type UserPublic = Pick<User, PublicUserKeys>;
export interface UserPrivate extends Pick<User, PrivateUserKeys> { export interface UserPrivate extends Pick<User, PrivateUserKeys> {
locale: string; locale: string;

View File

@ -0,0 +1,3 @@
export interface UserNoteUpdateSchema {
note: string;
}

View File

@ -16,10 +16,10 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { PublicConnectedAccount, UserPublic } from ".."; import { PublicConnectedAccount, PublicUserResponse } from "..";
export interface UserProfileResponse { export interface UserProfileResponse {
user: UserPublic; user: PublicUserResponse;
connected_accounts: PublicConnectedAccount; connected_accounts: PublicConnectedAccount;
premium_guild_since?: Date; premium_guild_since?: Date;
premium_since?: Date; premium_since?: Date;

View File

@ -69,6 +69,7 @@ export * from "./TotpSchema";
export * from "./UserDeleteSchema"; export * from "./UserDeleteSchema";
export * from "./UserGuildSettingsSchema"; export * from "./UserGuildSettingsSchema";
export * from "./UserModifySchema"; export * from "./UserModifySchema";
export * from "./UserNoteUpdateSchema";
export * from "./UserProfileModifySchema"; export * from "./UserProfileModifySchema";
export * from "./UserSettingsSchema"; export * from "./UserSettingsSchema";
export * from "./Validator"; export * from "./Validator";

View File

@ -0,0 +1,5 @@
export interface UserNoteResponse {
note: string;
note_user_id: string;
user_id: string;
}

View File

@ -1,7 +1,7 @@
import { PublicConnectedAccount, UserPublic } from "../../entities"; import { PublicConnectedAccount, PublicUser } from "../../entities";
export interface UserProfileResponse { export interface UserProfileResponse {
user: UserPublic; user: PublicUser;
connected_accounts: PublicConnectedAccount; connected_accounts: PublicConnectedAccount;
premium_guild_since?: Date; premium_guild_since?: Date;
premium_since?: Date; premium_since?: Date;

View File

@ -0,0 +1,8 @@
import { PublicUser, RelationshipType } from "../../entities";
export interface UserRelationshipsResponse {
id: string;
type: RelationshipType;
nickname: null;
user: PublicUser;
}

View File

@ -0,0 +1,22 @@
import { DmChannelDTO } from "../../dtos";
import { Guild, PrivateUser, PublicUser, User } from "../../entities";
export type PublicUserResponse = PublicUser;
export type PrivateUserResponse = PrivateUser;
export interface UserUpdateResponse extends PrivateUserResponse {
newToken?: string;
}
export type UserGuildsResponse = Guild[];
export type UserChannelsResponse = DmChannelDTO[];
export interface UserBackupCodesResponse {
expired: unknown;
user: User;
code: string;
consumed: boolean;
id: string;
}
[];

View File

@ -39,6 +39,9 @@ export * from "./OAuthAuthorizeResponse";
export * from "./StickerPacksResponse"; export * from "./StickerPacksResponse";
export * from "./Tenor"; export * from "./Tenor";
export * from "./TokenResponse"; export * from "./TokenResponse";
export * from "./UserNoteResponse";
export * from "./UserProfileResponse"; export * from "./UserProfileResponse";
export * from "./UserRelationshipsResponse";
export * from "./UserRelationsResponse"; export * from "./UserRelationsResponse";
export * from "./UserResponse";
export * from "./WebhookCreateResponse"; export * from "./WebhookCreateResponse";