Files
outline/server/models/Share.ts
Apoorv Mishra 79829a3129 Ability to create share url slug (#4550)
* feat: share url slug

* feat: add col urlId

* feat: allow updating urlId

* fix: typo

* fix: migrations

* fix: urlId model validation

* fix: input label

* fix: debounce slug request

* feat: link preview

* fix: send slug variant in response if available

* fix: temporary redirect to slug variant if available

* fix: move up the custom link field

* fix: process and display backend err

* fix: reset custom link state on popover close and remove isCopied

* fix: document link preview

* fix: set urlId when available

* fix: keep unique(urlId, teamId)

* fix: codeql

* fix: get rid of preview type

* fix: width not needed for block elem

* fix: migrations

* fix: array not required

* fix: use val

* fix: validation on shareId and test

* fix: allow clearing urlId

* fix: do not escape

* fix: unique error text

* fix: keep team
2022-12-13 17:26:36 -08:00

149 lines
2.7 KiB
TypeScript

import {
ForeignKey,
BelongsTo,
Column,
DefaultScope,
Table,
Scopes,
DataType,
Default,
AllowNull,
Is,
} from "sequelize-typescript";
import { SHARE_URL_SLUG_REGEX } from "@shared/utils/urlHelpers";
import Collection from "./Collection";
import Document from "./Document";
import Team from "./Team";
import User from "./User";
import IdModel from "./base/IdModel";
import Fix from "./decorators/Fix";
@DefaultScope(() => ({
include: [
{
association: "user",
paranoid: false,
},
{
association: "document",
required: false,
},
{
association: "team",
},
],
}))
@Scopes(() => ({
withCollectionPermissions: (userId: string) => {
return {
include: [
{
model: Document.scope("withDrafts"),
paranoid: true,
as: "document",
include: [
{
attributes: [
"id",
"permission",
"sharing",
"teamId",
"deletedAt",
],
model: Collection.scope({
method: ["withMembership", userId],
}),
as: "collection",
},
],
},
{
association: "user",
paranoid: false,
},
{
association: "team",
},
],
};
},
}))
@Table({ tableName: "shares", modelName: "share" })
@Fix
class Share extends IdModel {
@Column
published: boolean;
@Column
includeChildDocuments: boolean;
@Column
revokedAt: Date | null;
@Column
lastAccessedAt: Date | null;
/** Total count of times the shared link has been accessed */
@Default(0)
@Column
views: number;
@AllowNull
@Is({
args: SHARE_URL_SLUG_REGEX,
msg: "Must be only alphanumeric and dashes",
})
@Column
urlId: string | null | undefined;
// getters
get isRevoked() {
return !!this.revokedAt;
}
get canonicalUrl() {
return this.urlId
? `${this.team.url}/s/${this.urlId}`
: `${this.team.url}/s/${this.id}`;
}
// associations
@BelongsTo(() => User, "revokedById")
revokedBy: User;
@ForeignKey(() => User)
@Column(DataType.UUID)
revokedById: string;
@BelongsTo(() => User, "userId")
user: User;
@ForeignKey(() => User)
@Column(DataType.UUID)
userId: string;
@BelongsTo(() => Team, "teamId")
team: Team;
@ForeignKey(() => Team)
@Column(DataType.UUID)
teamId: string;
@BelongsTo(() => Document, "documentId")
document: Document | null;
@ForeignKey(() => Document)
@Column(DataType.UUID)
documentId: string;
revoke(userId: string) {
this.revokedAt = new Date();
this.revokedById = userId;
return this.save();
}
}
export default Share;