api: define the rest #33
232
api/main.tsp
232
api/main.tsp
@ -73,7 +73,7 @@ namespace Read {
|
||||
@get
|
||||
@operationId("GetSet")
|
||||
@route("/sets/{setID}")
|
||||
op set(@path setID: id, @query mediaType?: mediaType = mediaType.avif): SetResp | SetNotFound;
|
||||
op set(@path setID: id, @query mediaType?: mediaType = mediaType.webp): SetResp | SetNotFound;
|
||||
|
||||
/**
|
||||
* Operations for connected platforms.
|
||||
@ -100,7 +100,7 @@ namespace Read {
|
||||
@get
|
||||
@operationId("GetPlatformSet")
|
||||
@route("/set")
|
||||
op set(...platformParams, @query mediaType?: mediaType = mediaType.avif): SetResp | SetNotFound;
|
||||
op set(...platformParams, @query mediaType?: mediaType = mediaType.webp): SetResp | SetNotFound;
|
||||
|
||||
/**
|
||||
* Report used emotes.
|
||||
@ -132,12 +132,15 @@ namespace Read {
|
||||
@useAuth(BearerAuth)
|
||||
namespace EmoteManagement {
|
||||
/**
|
||||
* Image types accepted in uploads.
|
||||
* Image content for an emote upload.
|
||||
*/
|
||||
model Image is File<"image/png" | "image/jpeg" | "image/gif" | "image/webp" | "image/avif">;
|
||||
model Image {
|
||||
format: mediaType;
|
||||
data: bytes;
|
||||
}
|
||||
|
||||
/** Created emote. */
|
||||
model CreatedEmote is Success<Emote, 202>;
|
||||
model CreatedEmote is Success<Emote>;
|
||||
|
||||
/** Updated emote. */
|
||||
model UpdatedEmote is Success<Emote>;
|
||||
@ -147,20 +150,12 @@ namespace EmoteManagement {
|
||||
model ErrExcessiveUpload is Error<413>;
|
||||
|
||||
/**
|
||||
* Upload an emote.
|
||||
* Create a new emote.
|
||||
*/
|
||||
@post
|
||||
@operationId("CreateEmote")
|
||||
@route("/emotes")
|
||||
op new(
|
||||
@header contentType: "multipart/form-data",
|
||||
@multipartBody fields: {
|
||||
meta: HttpPart<Create<Emote> & {
|
||||
@header contentType: "application/json";
|
||||
}>;
|
||||
image: HttpPart<Image>;
|
||||
},
|
||||
): CreatedEmote | ErrBadParameters | ErrAuth | ErrExcessiveUpload;
|
||||
op new(emote: Emote): CreatedEmote | ErrBadParameters | ErrAuth | ErrExcessiveUpload;
|
||||
|
||||
/**
|
||||
* Modify an existing emote.
|
||||
@ -178,3 +173,210 @@ namespace EmoteManagement {
|
||||
@route("/emotes/{emoteID}")
|
||||
op delete(@path emoteID: id): NoContentResponse | ErrAuth;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Ligmotes User Management API.
|
||||
*/
|
||||
@useAuth(BearerAuth)
|
||||
namespace UserManagement {
|
||||
// NOTE(zephyr): creating users goes through the auth api, not this
|
||||
|
||||
/**
|
||||
* Update a user's name or picture.
|
||||
* Omitting the picture field in the request body causes the user's picture
|
||||
* to be removed.
|
||||
*/
|
||||
@patch(#{ implicitOptionality: false })
|
||||
@operationId("UpdateUser")
|
||||
@route("/users/{userID}")
|
||||
op update(@path userID: id, @body user: User): Success<User> | ErrAuth | ErrBadParameters | ErrNotFound;
|
||||
|
||||
/**
|
||||
* Soft-delete a user.
|
||||
*
|
||||
* Typically called by a user for themselves; moderators would use the
|
||||
* /moderate/user API to ban them instead.
|
||||
* This preserves the user's uploaded emotes as well as any moderation
|
||||
* reviews they may have performed.
|
||||
*/
|
||||
@delete
|
||||
@operationId("DeleteUser")
|
||||
@route("/users/{userID}")
|
||||
op delete(@path userID: id): Success<User> | ErrAuth | ErrNotFound;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Ligmotes Chat Management API.
|
||||
* Relates to managing emote sets and connections.
|
||||
*/
|
||||
@useAuth(BearerAuth)
|
||||
namespace ChatManagement {
|
||||
/**
|
||||
* ID and name of an emote within a set.
|
||||
* The same ID can appear multiple times in one set, but names are unique.
|
||||
*/
|
||||
model SetEmote {
|
||||
id: id;
|
||||
name: string;
|
||||
|
||||
/** RenameEmote may specify a new name for an existing emote in the set. */
|
||||
rename?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new emote set.
|
||||
* Note that creating a user should automatically create their first emote
|
||||
* set as well, without needing a call to this operation.
|
||||
*/
|
||||
@post
|
||||
@operationId("CreateEmoteSet")
|
||||
@route("/sets")
|
||||
op create(@body emoteSet: EmoteSet): Success<EmoteSet> | ErrAuth | ErrBadParameters;
|
||||
|
||||
/** Update the name of an existing emote set. */
|
||||
@patch(#{ implicitOptionality: false })
|
||||
@operationId("UpdateEmoteSet")
|
||||
@route("/sets/{setID}")
|
||||
op update(@path setID: id, @body emoteSet: EmoteSet): Success<EmoteSet> | ErrAuth | ErrBadParameters | ErrNotFound;
|
||||
|
||||
// NOTE(zephyr): We do not allow deleting emote sets.
|
||||
// That would enable many techniques to circumvent limits.
|
||||
|
||||
/** Add an emote to an emote set. */
|
||||
// TODO(zephyr): is returning the entire updated emote set too expensive?
|
||||
@post
|
||||
@operationId("AddEmote")
|
||||
@route("/sets/{setID}/emotes")
|
||||
op addEmote(@path setID: id, @body emote: SetEmote):
|
||||
| Success<EmoteSet>
|
||||
| ErrAuth
|
||||
| ErrBadParameters
|
||||
| ErrNotFound
|
||||
| ErrDuplicate;
|
||||
|
||||
/** Rename an emote that is already in an emote set. */
|
||||
@patch(#{ implicitOptionality: false })
|
||||
@operationId("RenameEmote")
|
||||
@route("/sets/{setID}/emotes")
|
||||
op renameEmote(@path setID: id, @body emote: SetEmote):
|
||||
| Success<EmoteSet>
|
||||
| ErrAuth
|
||||
| ErrBadParameters
|
||||
| ErrNotFound
|
||||
| ErrDuplicate;
|
||||
|
||||
/**
|
||||
* Remove an emote from an emote set.
|
||||
* This performs a soft-delete of the association between the two.
|
||||
*/
|
||||
@delete
|
||||
@operationId("RemoveEmote")
|
||||
@route("/sets/{setID}/emotes")
|
||||
op removeEmote(@path setID: id, @body emote: SetEmote): Success<EmoteSet> | ErrAuth | ErrNotFound;
|
||||
|
||||
/** Give a user editor permission for an emote set. */
|
||||
@post
|
||||
@operationId("AddEditor")
|
||||
@route("/sets/{setID}/editors")
|
||||
op addEditor(
|
||||
@path setID: id,
|
||||
@body editor: {
|
||||
userID: id;
|
||||
},
|
||||
): Success<EmoteSet> | ErrAuth | ErrNotFound | ErrDuplicate;
|
||||
|
||||
/** Remove a user's permission to edit an emote set. */
|
||||
@delete
|
||||
@operationId("RemoveEditor")
|
||||
@route("/sets/{setID}/editors")
|
||||
op removeEditor(
|
||||
@path setID: id,
|
||||
@body editor: {
|
||||
userID: id;
|
||||
},
|
||||
): Success<EmoteSet> | ErrAuth | ErrNotFound;
|
||||
|
||||
/** Get emote sets that the user is allowed to edit. */
|
||||
// TODO(zephyr): we should report this as a field on emote set objects as well
|
||||
@get
|
||||
@operationId("ListEditableSets")
|
||||
@route("/users/{userID}/sets")
|
||||
op listEditableSets(@path userID: id): Success<EmoteSet[]> | ErrAuth | ErrBadParameters;
|
||||
|
||||
/** Set an emote set as active for a connection. */
|
||||
@patch(#{ implicitOptionality: false })
|
||||
@operationId("UpdateActiveSet")
|
||||
@route("/platforms/{platform}/connections/{connectedID}/set")
|
||||
op updateActiveSet(
|
||||
@path platform: platform,
|
||||
@path connectedID: string,
|
||||
@body update: {
|
||||
setID: id;
|
||||
},
|
||||
): Success<EmoteSet> | ErrAuth | ErrBadParameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Moderation API.
|
||||
*/
|
||||
@useAuth(BearerAuth)
|
||||
@route("/moderate")
|
||||
namespace Moderation {
|
||||
/** Request body for updating emote approval status. */
|
||||
model EmoteStatus {
|
||||
approved: boolean;
|
||||
}
|
||||
|
||||
/** Options for deleting an emote. */
|
||||
model EmoteDeleteOptions {
|
||||
deleteMedia?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* List a selection of emotes awaiting moderator review.
|
||||
* NOTE(zephyr): Unlike other APIs, this returns the emotes directly.
|
||||
* We want all the information for reviewing, and a bit extra latency for
|
||||
* the join is less of an issue than it is in chat.
|
||||
*/
|
||||
@get
|
||||
@operationId("ListUnreviewedEmotes")
|
||||
@route("/emotes")
|
||||
op listUnreviewed(@query limit?: integer = 100): Success<Emote[]> | ErrAuth;
|
||||
|
||||
/**
|
||||
* Update an emote's approval status.
|
||||
*/
|
||||
@post
|
||||
@operationId("UpdateEmoteStatus")
|
||||
@route("/emotes/{emoteID}")
|
||||
op updateEmoteStatus(@path emoteID: id, @body status: EmoteStatus):
|
||||
| Success<Emote>
|
||||
| ErrAuth
|
||||
| ErrBadParameters
|
||||
| ErrNotFound;
|
||||
|
||||
/**
|
||||
* Fully remove an emote, e.g. for abusive or illegal content.
|
||||
*
|
||||
* This is a hard delete.
|
||||
* Typically, a call to it should have an accompanying call to ban the user.
|
||||
*/
|
||||
@delete
|
||||
@operationId("HardDeleteEmote")
|
||||
@route("/emotes/{emoteID}")
|
||||
op hardDeleteEmote(@path emoteID: id, @body options?: EmoteDeleteOptions):
|
||||
| NoContentResponse
|
||||
| ErrAuth
|
||||
| ErrBadParameters
|
||||
| ErrNotFound;
|
||||
|
||||
/**
|
||||
* Ban a user for a given reason.
|
||||
* This revokes all the user's current sessions, removes their active emote
|
||||
* sets, and enforces the restrictions described on UserBan.
|
||||
*/
|
||||
@post
|
||||
@operationId("Ban")
|
||||
@route("/users/{userID}")
|
||||
op ban(@path userID: id, @body details: UserBan): Success<User> | ErrAuth | ErrBadParameters | ErrNotFound;
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ model Success<T, Code extends 200 | 202 = 200> {
|
||||
* Error response.
|
||||
*/
|
||||
@error
|
||||
model Error<Code extends 400 | 401 | 403 | 404 | 405 | 413 | 500> {
|
||||
model Error<Code extends 400 | 401 | 403 | 404 | 405 | 409 | 413 | 429 | 500> {
|
||||
@statusCode
|
||||
code: Code;
|
||||
|
||||
@ -51,6 +51,14 @@ model ErrBadParameters is Error<400>;
|
||||
@error
|
||||
model ErrAuth is Error<401 | 403>;
|
||||
|
||||
/** Request specified an ID for a resource which does not exist. */
|
||||
@error
|
||||
model ErrNotFound is Error<404>;
|
||||
|
||||
/** Request tried to add an association which already exists. */
|
||||
@error
|
||||
model ErrDuplicate is Error<409>;
|
||||
|
||||
/**
|
||||
* ID of a user, emote, emote list, &c.
|
||||
* UUIDv7.
|
||||
@ -76,7 +84,7 @@ model Emote {
|
||||
name: string;
|
||||
|
||||
/** Links to and metadata about the available emote images. */
|
||||
@visibility(Lifecycle.Read)
|
||||
@visibility(Lifecycle.Read, Lifecycle.Create)
|
||||
media: Media[];
|
||||
|
||||
/** Emote color for sorting. */
|
||||
@ -99,7 +107,7 @@ model Emote {
|
||||
@visibility(Lifecycle.Read, Lifecycle.Create, Lifecycle.Update)
|
||||
attributions: Attribution[];
|
||||
|
||||
/** List of channels allowed to use the emote, if so restricted. */
|
||||
/** List of users allowed to use the emote, if so restricted. */
|
||||
@visibility(Lifecycle.Read, Lifecycle.Create, Lifecycle.Update)
|
||||
restrictions?: id[];
|
||||
|
||||
@ -145,12 +153,28 @@ model MinEmote {
|
||||
* Link and metadata for an emote image.
|
||||
*/
|
||||
model Media {
|
||||
@visibility(Lifecycle.Read, Lifecycle.Create)
|
||||
format: mediaType;
|
||||
|
||||
@visibility(Lifecycle.Read, Lifecycle.Create)
|
||||
scale: 1 | 2 | 4;
|
||||
|
||||
@visibility(Lifecycle.Create)
|
||||
data: bytes;
|
||||
|
||||
@visibility(Lifecycle.Read)
|
||||
hash: string;
|
||||
|
||||
@visibility(Lifecycle.Read)
|
||||
url: url;
|
||||
|
||||
@visibility(Lifecycle.Read)
|
||||
height: int16;
|
||||
|
||||
@visibility(Lifecycle.Read)
|
||||
width: int16;
|
||||
|
||||
@visibility(Lifecycle.Read)
|
||||
filesize: int32;
|
||||
}
|
||||
|
||||
@ -158,7 +182,7 @@ model Media {
|
||||
* Media formats that the CDN delivers.
|
||||
*/
|
||||
enum mediaType {
|
||||
avif: "AVIF",
|
||||
webp: "WebP",
|
||||
}
|
||||
|
||||
/**
|
||||
@ -198,10 +222,22 @@ model User {
|
||||
|
||||
/** User ban information. Only shown to the user themselves. */
|
||||
@visibility(Lifecycle.Read)
|
||||
ban?: {
|
||||
ban?: UserBan;
|
||||
}
|
||||
|
||||
/**
|
||||
* User ban details.
|
||||
* A banned user cannot use any authenticated APIs, meaning they cannot upload
|
||||
* or edit emotes, manage emote sets, or moderate.
|
||||
* They can still use the Read APIs and can still be issued sessions, allowing
|
||||
* them to sign in and see their ban reason.
|
||||
*/
|
||||
model UserBan {
|
||||
/** Reason for the ban. This is shown to the banned user. */
|
||||
reason: string;
|
||||
|
||||
/** Time the ban expires and the user regains full access to Ligmotes. */
|
||||
until: utcDateTime;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -5,4 +5,4 @@ options:
|
||||
emitter-output-dir: "{output-dir}/schema"
|
||||
openapi-versions:
|
||||
- 3.1.0
|
||||
output-dir: "{cwd}"
|
||||
output-dir: "{project-root}"
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -80,7 +80,8 @@ run = [
|
||||
[tasks."gen:api"]
|
||||
description = "Generate API scaffold"
|
||||
run = [
|
||||
'go tool oapi-codegen -config ./backend/rest/config.yaml ./api/schema/openapi.yaml',
|
||||
'tsp compile ./api',
|
||||
'go tool oapi-codegen -config ./backend/rest/config.yaml ./api/schema/openapi.v1.yaml',
|
||||
]
|
||||
|
||||
[env]
|
||||
|
Loading…
x
Reference in New Issue
Block a user