1
0
mirror of https://github.com/spacebarchat/server.git synced 2024-11-22 02:12:40 +01:00

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 44374 additions and 7224 deletions

View File

@ -3865,71 +3865,6 @@
"width"
]
},
"UserPublic": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"premium_since": {
"type": "string",
"format": "date-time"
},
"username": {
"type": "string"
},
"discriminator": {
"type": "string"
},
"public_flags": {
"type": "integer"
},
"avatar": {
"type": "string"
},
"accent_color": {
"type": "integer"
},
"banner": {
"type": "string"
},
"bio": {
"type": "string"
},
"bot": {
"type": "boolean"
},
"premium_type": {
"type": "integer"
},
"theme_colors": {
"type": "array",
"items": [
{
"type": "integer"
},
{
"type": "integer"
}
],
"minItems": 2,
"maxItems": 2
},
"pronouns": {
"type": "string"
}
},
"required": [
"bio",
"bot",
"discriminator",
"id",
"premium_since",
"premium_type",
"public_flags",
"username"
]
},
"PublicConnectedAccount": {
"type": "object",
"properties": {
@ -3948,6 +3883,78 @@
"type"
]
},
"DmChannelDTO": {
"type": "object",
"properties": {
"icon": {
"type": "string",
"nullable": true
},
"id": {
"type": "string"
},
"last_message_id": {
"type": "string",
"nullable": true
},
"name": {
"type": "string",
"nullable": true
},
"origin_channel_id": {
"type": "string",
"nullable": true
},
"owner_id": {
"type": "string"
},
"recipients": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MinimalPublicUserDTO"
}
},
"type": {
"type": "integer"
}
},
"required": [
"icon",
"id",
"last_message_id",
"name",
"origin_channel_id",
"recipients",
"type"
]
},
"MinimalPublicUserDTO": {
"type": "object",
"properties": {
"avatar": {
"type": "string",
"nullable": true
},
"discriminator": {
"type": "string"
},
"id": {
"type": "string"
},
"public_flags": {
"type": "integer"
},
"username": {
"type": "string"
}
},
"required": [
"discriminator",
"id",
"public_flags",
"username"
]
},
"ChannelPermissionOverwriteSchema": {
"type": "object",
"properties": {
@ -5386,6 +5393,17 @@
}
}
},
"UserNoteUpdateSchema": {
"type": "object",
"properties": {
"note": {
"type": "string"
}
},
"required": [
"note"
]
},
"UserProfileModifySchema": {
"type": "object",
"properties": {
@ -6581,11 +6599,30 @@
"token"
]
},
"UserNoteResponse": {
"type": "object",
"properties": {
"note": {
"type": "string"
},
"note_user_id": {
"type": "string"
},
"user_id": {
"type": "string"
}
},
"required": [
"note",
"note_user_id",
"user_id"
]
},
"UserProfileResponse": {
"type": "object",
"properties": {
"user": {
"$ref": "#/components/schemas/UserPublic"
"$ref": "#/components/schemas/PublicUser"
},
"connected_accounts": {
"$ref": "#/components/schemas/PublicConnectedAccount"
@ -6604,6 +6641,29 @@
"user"
]
},
"UserRelationshipsResponse": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"type": {
"$ref": "#/components/schemas/RelationshipType"
},
"nickname": {
"type": "null"
},
"user": {
"$ref": "#/components/schemas/PublicUser"
}
},
"required": [
"id",
"nickname",
"type",
"user"
]
},
"UserRelationsResponse": {
"type": "object",
"properties": {
@ -6633,6 +6693,255 @@
"object"
]
},
"PublicUserResponse": {
"$ref": "#/components/schemas/PublicUser"
},
"PrivateUserResponse": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"premium_since": {
"type": "string",
"format": "date-time"
},
"verified": {
"type": "boolean"
},
"username": {
"type": "string"
},
"discriminator": {
"type": "string"
},
"public_flags": {
"type": "integer"
},
"avatar": {
"type": "string"
},
"accent_color": {
"type": "integer"
},
"banner": {
"type": "string"
},
"bio": {
"type": "string"
},
"bot": {
"type": "boolean"
},
"premium_type": {
"type": "integer"
},
"theme_colors": {
"type": "array",
"items": [
{
"type": "integer"
},
{
"type": "integer"
}
],
"minItems": 2,
"maxItems": 2
},
"pronouns": {
"type": "string"
},
"flags": {
"type": "string"
},
"mfa_enabled": {
"type": "boolean"
},
"email": {
"type": "string"
},
"phone": {
"type": "string"
},
"nsfw_allowed": {
"type": "boolean"
},
"premium": {
"type": "boolean"
},
"purchased_flags": {
"type": "integer"
},
"premium_usage_flags": {
"type": "integer"
},
"disabled": {
"type": "boolean"
}
},
"required": [
"bio",
"bot",
"disabled",
"discriminator",
"flags",
"id",
"mfa_enabled",
"nsfw_allowed",
"premium",
"premium_since",
"premium_type",
"premium_usage_flags",
"public_flags",
"purchased_flags",
"username",
"verified"
]
},
"UserUpdateResponse": {
"type": "object",
"properties": {
"newToken": {
"type": "string"
},
"id": {
"type": "string"
},
"premium_since": {
"type": "string",
"format": "date-time"
},
"verified": {
"type": "boolean"
},
"username": {
"type": "string"
},
"discriminator": {
"type": "string"
},
"public_flags": {
"type": "integer"
},
"avatar": {
"type": "string"
},
"accent_color": {
"type": "integer"
},
"banner": {
"type": "string"
},
"bio": {
"type": "string"
},
"bot": {
"type": "boolean"
},
"premium_type": {
"type": "integer"
},
"theme_colors": {
"type": "array",
"items": [
{
"type": "integer"
},
{
"type": "integer"
}
],
"minItems": 2,
"maxItems": 2
},
"pronouns": {
"type": "string"
},
"flags": {
"type": "string"
},
"mfa_enabled": {
"type": "boolean"
},
"email": {
"type": "string"
},
"phone": {
"type": "string"
},
"nsfw_allowed": {
"type": "boolean"
},
"premium": {
"type": "boolean"
},
"purchased_flags": {
"type": "integer"
},
"premium_usage_flags": {
"type": "integer"
},
"disabled": {
"type": "boolean"
}
},
"required": [
"bio",
"bot",
"disabled",
"discriminator",
"flags",
"id",
"mfa_enabled",
"nsfw_allowed",
"premium",
"premium_since",
"premium_type",
"premium_usage_flags",
"public_flags",
"purchased_flags",
"username",
"verified"
]
},
"UserGuildsResponse": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Guild"
}
},
"UserChannelsResponse": {
"type": "array",
"items": {
"$ref": "#/components/schemas/DmChannelDTO"
}
},
"UserBackupCodesResponse": {
"type": "object",
"properties": {
"expired": {},
"user": {
"$ref": "#/components/schemas/User"
},
"code": {
"type": "string"
},
"consumed": {
"type": "boolean"
},
"id": {
"type": "string"
}
},
"required": [
"code",
"consumed",
"expired",
"id",
"user"
]
},
"WebhookCreateResponse": {
"type": "object",
"properties": {
@ -6841,8 +7150,35 @@
}
},
"responses": {
"default": {
"description": "No description available"
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UserSettings"
}
}
}
},
"400": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/APIErrorResponse"
}
}
}
},
"404": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/APIErrorResponse"
}
}
}
}
},
"tags": [
@ -6868,8 +7204,28 @@
}
},
"responses": {
"default": {
"204": {
"description": "No description available"
},
"400": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/APIErrorResponse"
}
}
}
},
"404": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/APIErrorResponse"
}
}
}
}
},
"tags": [
@ -6885,8 +7241,28 @@
}
],
"responses": {
"default": {
"204": {
"description": "No description available"
},
"400": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/APIErrorResponse"
}
}
}
},
"404": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/APIErrorResponse"
}
}
}
}
},
"parameters": [
@ -6912,9 +7288,29 @@
"bearer": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UserNoteUpdateSchema"
}
}
}
},
"responses": {
"default": {
"204": {
"description": "No description available"
},
"404": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/APIErrorResponse"
}
}
}
}
},
"parameters": [
@ -7049,6 +7445,8 @@
"bearer": []
}
],
"description": "This route is replaced with users/@me/mfa/codes-verification in newer clients",
"deprecated": true,
"requestBody": {
"required": true,
"content": {
@ -7060,8 +7458,35 @@
}
},
"responses": {
"default": {
"description": "No description available"
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UserBackupCodesResponse"
}
}
}
},
"400": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/APIErrorResponse"
}
}
}
},
"404": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/APIErrorResponse"
}
}
}
}
},
"tags": [
@ -7087,8 +7512,35 @@
}
},
"responses": {
"default": {
"description": "No description available"
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UserBackupCodesResponse"
}
}
}
},
"400": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/APIErrorResponse"
}
}
}
},
"404": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/APIErrorResponse"
}
}
}
}
},
"tags": [
@ -7131,8 +7583,35 @@
}
},
"responses": {
"default": {
"description": "No description available"
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UserUpdateResponse"
}
}
}
},
"400": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/APIErrorResponse"
}
}
}
},
"404": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/APIErrorResponse"
}
}
}
}
},
"tags": [
@ -7148,8 +7627,15 @@
}
],
"responses": {
"default": {
"description": "No description available"
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UserGuildsResponse"
}
}
}
}
},
"tags": [
@ -7165,8 +7651,28 @@
}
],
"responses": {
"default": {
"204": {
"description": "No description available"
},
"400": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/APIErrorResponse"
}
}
}
},
"404": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/APIErrorResponse"
}
}
}
}
},
"parameters": [
@ -7282,8 +7788,28 @@
}
],
"responses": {
"default": {
"204": {
"description": "No description available"
},
"400": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/APIErrorResponse"
}
}
}
},
"404": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/APIErrorResponse"
}
}
}
}
},
"tags": [
@ -7316,8 +7842,28 @@
}
],
"responses": {
"default": {
"204": {
"description": "No description available"
},
"401": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/APIErrorResponse"
}
}
}
},
"404": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/APIErrorResponse"
}
}
}
}
},
"tags": [
@ -7434,8 +7980,15 @@
}
},
"responses": {
"default": {
"description": "No description available"
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/DmChannelDTO"
}
}
}
}
},
"tags": [
@ -7590,6 +8143,16 @@
}
}
}
},
"404": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/APIErrorResponse"
}
}
}
}
},
"parameters": [
@ -7654,8 +8217,15 @@
}
],
"responses": {
"default": {
"description": "No description available"
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PublicUserResponse"
}
}
}
}
},
"parameters": [
@ -7683,8 +8253,28 @@
}
],
"responses": {
"default": {
"204": {
"description": "No description available"
},
"403": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/APIErrorResponse"
}
}
}
},
"404": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/APIErrorResponse"
}
}
}
}
},
"parameters": [
@ -11252,7 +11842,7 @@
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UserPublic"
"$ref": "#/components/schemas/PublicUser"
}
}
}

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

@ -16,16 +16,26 @@
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 { User } from "@spacebar/util";
import { Request, Response, Router } from "express";
const router: Router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
const { id } = req.params;
router.get(
"/",
route({
responses: {
200: {
body: "PublicUserResponse",
},
},
}),
async (req: Request, res: Response) => {
const { id } = req.params;
res.json(await User.getPublicUser(id));
});
res.json(await User.getPublicUser(id));
},
);
export default router;

View File

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

View File

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

View File

@ -16,41 +16,58 @@
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 { Member, User } from "@spacebar/util";
import bcrypt from "bcrypt";
import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server";
const router = Router();
router.post("/", route({}), async (req: Request, res: Response) => {
const user = await User.findOneOrFail({
where: { id: req.user_id },
select: ["data"],
}); //User object
let correctpass = true;
router.post(
"/",
route({
responses: {
204: {},
401: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const user = await User.findOneOrFail({
where: { id: req.user_id },
select: ["data"],
}); //User object
let correctpass = true;
if (user.data.hash) {
// guest accounts can delete accounts without password
correctpass = await bcrypt.compare(req.body.password, user.data.hash);
if (!correctpass) {
throw new HTTPError(req.t("auth:login.INVALID_PASSWORD"));
if (user.data.hash) {
// guest accounts can delete accounts without password
correctpass = await bcrypt.compare(
req.body.password,
user.data.hash,
);
if (!correctpass) {
throw new HTTPError(req.t("auth:login.INVALID_PASSWORD"));
}
}
}
// TODO: decrement guild member count
// TODO: decrement guild member count
if (correctpass) {
await Promise.all([
User.delete({ id: req.user_id }),
Member.delete({ id: req.user_id }),
]);
if (correctpass) {
await Promise.all([
User.delete({ id: req.user_id }),
Member.delete({ id: req.user_id }),
]);
res.sendStatus(204);
} else {
res.sendStatus(401);
}
});
res.sendStatus(204);
} else {
res.sendStatus(401);
}
},
);
export default router;

View File

@ -16,35 +16,52 @@
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 { User } from "@spacebar/util";
import bcrypt from "bcrypt";
import { Request, Response, Router } from "express";
const router = Router();
router.post("/", route({}), async (req: Request, res: Response) => {
const user = await User.findOneOrFail({
where: { id: req.user_id },
select: ["data"],
}); //User object
let correctpass = true;
router.post(
"/",
route({
responses: {
204: {},
400: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const user = await User.findOneOrFail({
where: { id: req.user_id },
select: ["data"],
}); //User object
let correctpass = true;
if (user.data.hash) {
// guest accounts can delete accounts without password
correctpass = await bcrypt.compare(req.body.password, user.data.hash); //Not sure if user typed right password :/
}
if (user.data.hash) {
// guest accounts can delete accounts without password
correctpass = await bcrypt.compare(
req.body.password,
user.data.hash,
); //Not sure if user typed right password :/
}
if (correctpass) {
await User.update({ id: req.user_id }, { disabled: true });
if (correctpass) {
await User.update({ id: req.user_id }, { disabled: true });
res.sendStatus(204);
} else {
res.status(400).json({
message: "Password does not match",
code: 50018,
});
}
});
res.sendStatus(204);
} else {
res.status(400).json({
message: "Password does not match",
code: 50018,
});
}
},
);
export default router;

View File

@ -16,79 +16,106 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Router, Request, Response } from "express";
import { route } from "@spacebar/api";
import {
Config,
Guild,
Member,
User,
GuildDeleteEvent,
GuildMemberRemoveEvent,
Member,
User,
emitEvent,
Config,
} from "@spacebar/util";
import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server";
import { route } from "@spacebar/api";
const router: Router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
const members = await Member.find({
relations: ["guild"],
where: { id: req.user_id },
});
router.get(
"/",
route({
responses: {
200: {
body: "UserGuildsResponse",
},
},
}),
async (req: Request, res: Response) => {
const members = await Member.find({
relations: ["guild"],
where: { id: req.user_id },
});
let guild = members.map((x) => x.guild);
let guild = members.map((x) => x.guild);
if ("with_counts" in req.query && req.query.with_counts == "true") {
guild = []; // TODO: Load guilds with user role permissions number
}
if ("with_counts" in req.query && req.query.with_counts == "true") {
guild = []; // TODO: Load guilds with user role permissions number
}
res.json(guild);
});
res.json(guild);
},
);
// user send to leave a certain guild
router.delete("/:guild_id", route({}), async (req: Request, res: Response) => {
const { autoJoin } = Config.get().guild;
const { guild_id } = req.params;
const guild = await Guild.findOneOrFail({
where: { id: guild_id },
select: ["owner_id"],
});
if (!guild) throw new HTTPError("Guild doesn't exist", 404);
if (guild.owner_id === req.user_id)
throw new HTTPError("You can't leave your own guild", 400);
if (
autoJoin.enabled &&
autoJoin.guilds.includes(guild_id) &&
!autoJoin.canLeave
) {
throw new HTTPError("You can't leave instance auto join guilds", 400);
}
await Promise.all([
Member.delete({ id: req.user_id, guild_id: guild_id }),
emitEvent({
event: "GUILD_DELETE",
data: {
id: guild_id,
router.delete(
"/:guild_id",
route({
responses: {
204: {},
400: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
user_id: req.user_id,
} as GuildDeleteEvent),
]);
const user = await User.getPublicUser(req.user_id);
await emitEvent({
event: "GUILD_MEMBER_REMOVE",
data: {
guild_id: guild_id,
user: user,
},
guild_id: guild_id,
} as GuildMemberRemoveEvent);
}),
async (req: Request, res: Response) => {
const { autoJoin } = Config.get().guild;
const { guild_id } = req.params;
const guild = await Guild.findOneOrFail({
where: { id: guild_id },
select: ["owner_id"],
});
return res.sendStatus(204);
});
if (!guild) throw new HTTPError("Guild doesn't exist", 404);
if (guild.owner_id === req.user_id)
throw new HTTPError("You can't leave your own guild", 400);
if (
autoJoin.enabled &&
autoJoin.guilds.includes(guild_id) &&
!autoJoin.canLeave
) {
throw new HTTPError(
"You can't leave instance auto join guilds",
400,
);
}
await Promise.all([
Member.delete({ id: req.user_id, guild_id: guild_id }),
emitEvent({
event: "GUILD_DELETE",
data: {
id: guild_id,
},
user_id: req.user_id,
} as GuildDeleteEvent),
]);
const user = await User.getPublicUser(req.user_id);
await emitEvent({
event: "GUILD_MEMBER_REMOVE",
data: {
guild_id: guild_id,
user: user,
},
guild_id: guild_id,
} as GuildMemberRemoveEvent);
return res.sendStatus(204);
},
);
export default router;

View File

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

View File

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

View File

@ -33,7 +33,23 @@ const router = Router();
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) => {
const { password, regenerate } = req.body as MfaCodesSchema;

View File

@ -16,71 +16,99 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Request, Response, Router } from "express";
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();
router.get("/:id", route({}), async (req: Request, res: Response) => {
const { id } = req.params;
const note = await Note.findOneOrFail({
where: {
owner: { id: req.user_id },
target: { id: id },
router.get(
"/:id",
route({
responses: {
200: {
body: "UserNoteResponse",
},
404: {
body: "APIErrorResponse",
},
},
});
}),
async (req: Request, res: Response) => {
const { id } = req.params;
return res.json({
note: note?.content,
note_user_id: id,
user_id: req.user_id,
});
});
const note = await Note.findOneOrFail({
where: {
owner: { id: req.user_id },
target: { id: id },
},
});
router.put("/:id", route({}), async (req: Request, res: Response) => {
const { id } = req.params;
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 { note } = req.body;
return res.json({
note: note?.content,
note_user_id: id,
user_id: req.user_id,
});
},
);
if (note && note.length) {
// upsert a note
if (
await Note.findOne({
where: { owner: { id: owner.id }, target: { id: target.id } },
})
) {
Note.update(
{ owner: { id: owner.id }, target: { id: target.id } },
{ owner, target, content: note },
);
router.put(
"/:id",
route({
requestBody: "UserNoteUpdateSchema",
responses: {
204: {},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { id } = req.params;
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 { note } = req.body;
if (note && note.length) {
// upsert a note
if (
await Note.findOne({
where: {
owner: { id: owner.id },
target: { id: target.id },
},
})
) {
Note.update(
{ owner: { id: owner.id }, target: { id: target.id } },
{ owner, target, content: note },
);
} else {
Note.insert({
id: Snowflake.generate(),
owner,
target,
content: note,
});
}
} else {
Note.insert({
id: Snowflake.generate(),
owner,
target,
content: note,
await Note.delete({
owner: { id: owner.id },
target: { id: target.id },
});
}
} else {
await Note.delete({
owner: { id: owner.id },
target: { id: target.id },
await emitEvent({
event: "USER_NOTE_UPDATE",
data: {
note: note,
id: target.id,
},
user_id: owner.id,
});
}
await emitEvent({
event: "USER_NOTE_UPDATE",
data: {
note: note,
id: target.id,
},
user_id: owner.id,
});
return res.status(204);
});
return res.status(204);
},
);
export default router;

View File

@ -38,29 +38,53 @@ const userProjection: (keyof User)[] = [
...PublicUserProjection,
];
router.get("/", route({}), async (req: Request, res: Response) => {
const user = await User.findOneOrFail({
where: { id: req.user_id },
relations: ["relationships", "relationships.to"],
select: ["id", "relationships"],
});
router.get(
"/",
route({
responses: {
200: {
body: "UserRelationshipsResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const user = await User.findOneOrFail({
where: { id: req.user_id },
relations: ["relationships", "relationships.to"],
select: ["id", "relationships"],
});
//TODO DTO
const related_users = user.relationships.map((r) => {
return {
id: r.to.id,
type: r.type,
nickname: null,
user: r.to.toPublicUser(),
};
});
//TODO DTO
const related_users = user.relationships.map((r) => {
return {
id: r.to.id,
type: r.type,
nickname: null,
user: r.to.toPublicUser(),
};
});
return res.json(related_users);
});
return res.json(related_users);
},
);
router.put(
"/:id",
route({ requestBody: "RelationshipPutSchema" }),
route({
requestBody: "RelationshipPutSchema",
responses: {
204: {},
400: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
return await updateRelationship(
req,
@ -77,7 +101,18 @@ router.put(
router.post(
"/",
route({ requestBody: "RelationshipPostSchema" }),
route({
requestBody: "RelationshipPostSchema",
responses: {
204: {},
400: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
return await updateRelationship(
req,
@ -98,64 +133,78 @@ router.post(
},
);
router.delete("/:id", route({}), async (req: Request, res: Response) => {
const { id } = req.params;
if (id === req.user_id)
throw new HTTPError("You can't remove yourself as a friend");
router.delete(
"/:id",
route({
responses: {
204: {},
400: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { id } = req.params;
if (id === req.user_id)
throw new HTTPError("You can't remove yourself as a friend");
const user = await User.findOneOrFail({
where: { id: req.user_id },
select: userProjection,
relations: ["relationships"],
});
const friend = await User.findOneOrFail({
where: { id: id },
select: userProjection,
relations: ["relationships"],
});
const user = await User.findOneOrFail({
where: { id: req.user_id },
select: userProjection,
relations: ["relationships"],
});
const friend = await User.findOneOrFail({
where: { id: id },
select: userProjection,
relations: ["relationships"],
});
const relationship = user.relationships.find((x) => x.to_id === id);
const friendRequest = friend.relationships.find(
(x) => x.to_id === req.user_id,
);
const relationship = user.relationships.find((x) => x.to_id === id);
const friendRequest = friend.relationships.find(
(x) => x.to_id === req.user_id,
);
if (!relationship)
throw new HTTPError("You are not friends with the user", 404);
if (relationship?.type === RelationshipType.blocked) {
// unblock user
if (!relationship)
throw new HTTPError("You are not friends with the user", 404);
if (relationship?.type === RelationshipType.blocked) {
// unblock user
await Promise.all([
Relationship.delete({ id: relationship.id }),
emitEvent({
event: "RELATIONSHIP_REMOVE",
user_id: req.user_id,
data: relationship.toPublicRelationship(),
} as RelationshipRemoveEvent),
]);
return res.sendStatus(204);
}
if (friendRequest && friendRequest.type !== RelationshipType.blocked) {
await Promise.all([
Relationship.delete({ id: friendRequest.id }),
await emitEvent({
event: "RELATIONSHIP_REMOVE",
data: friendRequest.toPublicRelationship(),
user_id: id,
} as RelationshipRemoveEvent),
]);
}
await Promise.all([
Relationship.delete({ id: relationship.id }),
emitEvent({
event: "RELATIONSHIP_REMOVE",
user_id: req.user_id,
data: relationship.toPublicRelationship(),
user_id: req.user_id,
} as RelationshipRemoveEvent),
]);
return res.sendStatus(204);
}
if (friendRequest && friendRequest.type !== RelationshipType.blocked) {
await Promise.all([
Relationship.delete({ id: friendRequest.id }),
await emitEvent({
event: "RELATIONSHIP_REMOVE",
data: friendRequest.toPublicRelationship(),
user_id: id,
} as RelationshipRemoveEvent),
]);
}
await Promise.all([
Relationship.delete({ id: relationship.id }),
emitEvent({
event: "RELATIONSHIP_REMOVE",
data: relationship.toPublicRelationship(),
user_id: req.user_id,
} as RelationshipRemoveEvent),
]);
return res.sendStatus(204);
});
},
);
export default router;

View File

@ -22,17 +22,43 @@ import { Request, Response, Router } from "express";
const router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
const user = await User.findOneOrFail({
where: { id: req.user_id },
relations: ["settings"],
});
return res.json(user.settings);
});
router.get(
"/",
route({
responses: {
200: {
body: "UserSettings",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const user = await User.findOneOrFail({
where: { id: req.user_id },
relations: ["settings"],
});
return res.json(user.settings);
},
);
router.patch(
"/",
route({ requestBody: "UserSettingsSchema" }),
route({
requestBody: "UserSettingsSchema",
responses: {
200: {
body: "UserSettings",
},
400: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const body = req.body as UserSettingsSchema;
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[];
};
};
deprecated?: boolean;
// test?: {
// response?: RouteResponse;
// body?: unknown;

View File

@ -86,8 +86,7 @@ export const PrivateUserProjection = [
// Private user data that should never get sent to the client
export type PublicUser = Pick<User, PublicUserKeys>;
export type UserPublic = Pick<User, PublicUserKeys>;
export type PrivateUser = Pick<User, PrivateUserKeys>;
export interface UserPrivate extends Pick<User, PrivateUserKeys> {
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/>.
*/
import { PublicConnectedAccount, UserPublic } from "..";
import { PublicConnectedAccount, PublicUserResponse } from "..";
export interface UserProfileResponse {
user: UserPublic;
user: PublicUserResponse;
connected_accounts: PublicConnectedAccount;
premium_guild_since?: Date;
premium_since?: Date;

View File

@ -69,6 +69,7 @@ export * from "./TotpSchema";
export * from "./UserDeleteSchema";
export * from "./UserGuildSettingsSchema";
export * from "./UserModifySchema";
export * from "./UserNoteUpdateSchema";
export * from "./UserProfileModifySchema";
export * from "./UserSettingsSchema";
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 {
user: UserPublic;
user: PublicUser;
connected_accounts: PublicConnectedAccount;
premium_guild_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 "./Tenor";
export * from "./TokenResponse";
export * from "./UserNoteResponse";
export * from "./UserProfileResponse";
export * from "./UserRelationshipsResponse";
export * from "./UserRelationsResponse";
export * from "./UserResponse";
export * from "./WebhookCreateResponse";