diff --git a/packages/backend/src/misc/fetch-meta.ts b/packages/backend/src/misc/fetch-meta.ts
index 4fb69f923..3d66f2d63 100644
--- a/packages/backend/src/misc/fetch-meta.ts
+++ b/packages/backend/src/misc/fetch-meta.ts
@@ -1,6 +1,7 @@
import { db } from "@/db/postgre.js";
import { Meta } from "@/models/entities/meta.js";
import push from 'web-push';
+import { Metas } from "@/models/index.js";
let cache: Meta;
@@ -33,41 +34,31 @@ export function metaToPugArgs(meta: Meta): object {
export async function fetchMeta(noCache = false): Promise {
if (!noCache && cache) return cache;
- return await db.transaction(async (transactionalEntityManager) => {
- // New IDs are prioritized because multiple records may have been created due to past bugs.
- const metas = await transactionalEntityManager.find(Meta, {
- order: {
- id: "DESC",
- },
- });
-
- const meta = metas[0];
-
- if (meta) {
- cache = meta;
- return meta;
- } else {
- const { publicKey, privateKey } = push.generateVAPIDKeys();
-
- // If fetchMeta is called at the same time when meta is empty, this part may be called at the same time, so use fail-safe upsert.
- const saved = await transactionalEntityManager
- .upsert(
- Meta,
- {
- id: "x",
- swPublicKey: publicKey,
- swPrivateKey: privateKey,
- },
- ["id"],
- )
- .then((x) =>
- transactionalEntityManager.findOneByOrFail(Meta, x.identifiers[0]),
- );
-
- cache = saved;
- return saved;
- }
+ // New IDs are prioritized because multiple records may have been created due to past bugs.
+ const meta = await Metas.findOne({
+ where: {},
+ order: {
+ id: "DESC",
+ },
});
+
+ if (meta) {
+ cache = meta;
+ return meta;
+ }
+
+ const { publicKey, privateKey } = push.generateVAPIDKeys();
+ const data = {
+ id: "x",
+ swPublicKey: publicKey,
+ swPrivateKey: privateKey,
+ };
+
+ // If fetchMeta is called at the same time when meta is empty, this part may be called at the same time, so use fail-safe upsert.
+ await Metas.upsert(data, ["id"]);
+
+ cache = await Metas.findOneByOrFail({ id: data.id });
+ return cache;
}
setInterval(() => {
diff --git a/packages/backend/src/remote/activitypub/models/person.ts b/packages/backend/src/remote/activitypub/models/person.ts
index e474e1f54..0812f7e7d 100644
--- a/packages/backend/src/remote/activitypub/models/person.ts
+++ b/packages/backend/src/remote/activitypub/models/person.ts
@@ -294,80 +294,77 @@ export async function createPerson(
}
}
- // Create user
- let user: IRemoteUser;
+ // Prepare objects
+ let user = new User({
+ id: genId(),
+ avatarId: null,
+ bannerId: null,
+ createdAt: new Date(),
+ lastFetchedAt: new Date(),
+ name: truncate(person.name, nameLength),
+ isLocked: !!person.manuallyApprovesFollowers,
+ movedToUri: person.movedTo,
+ alsoKnownAs: person.alsoKnownAs,
+ isExplorable: !!person.discoverable,
+ username: person.preferredUsername,
+ usernameLower: person.preferredUsername!.toLowerCase(),
+ host,
+ inbox: person.inbox,
+ sharedInbox:
+ person.sharedInbox ||
+ (person.endpoints ? person.endpoints.sharedInbox : undefined),
+ followersUri: person.followers
+ ? getApId(person.followers)
+ : undefined,
+ followersCount:
+ followersCount !== undefined
+ ? followersCount
+ : person.followers &&
+ typeof person.followers !== "string" &&
+ isCollectionOrOrderedCollection(person.followers)
+ ? person.followers.totalItems
+ : undefined,
+ followingCount:
+ followingCount !== undefined
+ ? followingCount
+ : person.following &&
+ typeof person.following !== "string" &&
+ isCollectionOrOrderedCollection(person.following)
+ ? person.following.totalItems
+ : undefined,
+ featured: person.featured ? getApId(person.featured) : undefined,
+ uri: person.id,
+ tags,
+ isBot,
+ isCat: (person as any).isCat === true,
+ }) as IRemoteUser;
+
+ const profile = new UserProfile({
+ userId: user.id,
+ description: person.summary
+ ? await htmlToMfm(truncate(person.summary, summaryLength), person.tag)
+ : null,
+ url: url,
+ fields,
+ birthday: bday ? bday[0] : null,
+ location: person["vcard:Address"] || null,
+ userHost: host,
+ });
+
+ const publicKey = person.publicKey
+ ? new UserPublickey({
+ userId: user.id,
+ keyId: person.publicKey.id,
+ keyPem: person.publicKey.publicKeyPem,
+ })
+ : null;
+
try {
- // Start transaction
+ // Save the objects atomically using a db transaction, note that we should never run any code in a transaction block directly
await db.transaction(async (transactionalEntityManager) => {
- user = (await transactionalEntityManager.save(
- new User({
- id: genId(),
- avatarId: null,
- bannerId: null,
- createdAt: new Date(),
- lastFetchedAt: new Date(),
- name: truncate(person.name, nameLength),
- isLocked: !!person.manuallyApprovesFollowers,
- movedToUri: person.movedTo,
- alsoKnownAs: person.alsoKnownAs,
- isExplorable: !!person.discoverable,
- username: person.preferredUsername,
- usernameLower: person.preferredUsername!.toLowerCase(),
- host,
- inbox: person.inbox,
- sharedInbox:
- person.sharedInbox ||
- (person.endpoints ? person.endpoints.sharedInbox : undefined),
- followersUri: person.followers
- ? getApId(person.followers)
- : undefined,
- followersCount:
- followersCount !== undefined
- ? followersCount
- : person.followers &&
- typeof person.followers !== "string" &&
- isCollectionOrOrderedCollection(person.followers)
- ? person.followers.totalItems
- : undefined,
- followingCount:
- followingCount !== undefined
- ? followingCount
- : person.following &&
- typeof person.following !== "string" &&
- isCollectionOrOrderedCollection(person.following)
- ? person.following.totalItems
- : undefined,
- featured: person.featured ? getApId(person.featured) : undefined,
- uri: person.id,
- tags,
- isBot,
- isCat: (person as any).isCat === true,
- }),
- )) as IRemoteUser;
-
- await transactionalEntityManager.save(
- new UserProfile({
- userId: user.id,
- description: person.summary
- ? await htmlToMfm(truncate(person.summary, summaryLength), person.tag)
- : null,
- url: url,
- fields,
- birthday: bday ? bday[0] : null,
- location: person["vcard:Address"] || null,
- userHost: host,
- }),
- );
-
- if (person.publicKey) {
- await transactionalEntityManager.save(
- new UserPublickey({
- userId: user.id,
- keyId: person.publicKey.id,
- keyPem: person.publicKey.publicKeyPem,
- }),
- );
- }
+ await transactionalEntityManager.save(user);
+ await transactionalEntityManager.save(profile);
+ if (publicKey) await transactionalEntityManager.save(publicKey);
});
} catch (e) {
// duplicate key error
@@ -754,21 +751,23 @@ export async function updateFeatured(userId: User["id"], resolver?: Resolver, li
.map((item) => limit(() => resolveNote(item, resolver, limiter))),
);
- await db.transaction(async (transactionalEntityManager) => {
- await transactionalEntityManager.delete(UserNotePining, {
+ // Prepare the objects
+ // For now, generate the id at a different time and maintain the order.
+ const data: Partial[] = [];
+ let td = 0;
+ for (const note of featuredNotes.filter((note) => note != null)) {
+ td -= 1000;
+ data.push({
+ id: genId(new Date(Date.now() + td)),
+ createdAt: new Date(),
userId: user.id,
+ noteId: note!.id,
});
+ }
- // For now, generate the id at a different time and maintain the order.
- let td = 0;
- for (const note of featuredNotes.filter((note) => note != null)) {
- td -= 1000;
- transactionalEntityManager.insert(UserNotePining, {
- id: genId(new Date(Date.now() + td)),
- createdAt: new Date(),
- userId: user.id,
- noteId: note!.id,
- });
- }
+ // Save the objects atomically using a db transaction, note that we should never run any code in a transaction block directly
+ await db.transaction(async (transactionalEntityManager) => {
+ await transactionalEntityManager.delete(UserNotePining, { userId: user.id });
+ await transactionalEntityManager.insert(UserNotePining, data);
});
}
diff --git a/packages/backend/src/server/api/common/signup.ts b/packages/backend/src/server/api/common/signup.ts
index 6beae2c78..19d4f17aa 100644
--- a/packages/backend/src/server/api/common/signup.ts
+++ b/packages/backend/src/server/api/common/signup.ts
@@ -84,58 +84,55 @@ export async function signup(opts: {
),
);
- let account!: User;
-
- // Start transaction
- await db.transaction(async (transactionalEntityManager) => {
- const exist = await transactionalEntityManager.findOneBy(User, {
- usernameLower: username.toLowerCase(),
- host: IsNull(),
- });
-
- if (exist) throw new Error(" the username is already used");
-
- account = await transactionalEntityManager.save(
- new User({
- id: genId(),
- createdAt: new Date(),
- username: username,
- usernameLower: username.toLowerCase(),
- host: toPunyNullable(host),
- token: secret,
- isAdmin:
- (await Users.countBy({
- host: IsNull(),
- isAdmin: true,
- })) === 0,
- }),
- );
-
- await transactionalEntityManager.save(
- new UserKeypair({
- publicKey: keyPair[0],
- privateKey: keyPair[1],
- userId: account.id,
- }),
- );
-
- await transactionalEntityManager.save(
- new UserProfile({
- userId: account.id,
- autoAcceptFollowed: true,
- password: hash,
- }),
- );
-
- await transactionalEntityManager.save(
- new UsedUsername({
- createdAt: new Date(),
- username: username.toLowerCase(),
- }),
- );
+ const exist = await Users.findOneBy({
+ usernameLower: username.toLowerCase(),
+ host: IsNull(),
});
- usersChart.update(account, true);
+ if (exist) throw new Error("The username is already in use");
+ // Prepare objects
+ const user = new User({
+ id: genId(),
+ createdAt: new Date(),
+ username: username,
+ usernameLower: username.toLowerCase(),
+ host: toPunyNullable(host),
+ token: secret,
+ isAdmin:
+ (await Users.countBy({
+ host: IsNull(),
+ isAdmin: true,
+ })) === 0,
+ });
+
+ const userKeypair = new UserKeypair({
+ publicKey: keyPair[0],
+ privateKey: keyPair[1],
+ userId: user.id,
+ });
+
+ const userProfile = new UserProfile({
+ userId: user.id,
+ autoAcceptFollowed: true,
+ password: hash,
+ });
+
+ const usedUsername = new UsedUsername({
+ createdAt: new Date(),
+ username: username.toLowerCase(),
+ });
+
+ // Save the objects atomically using a db transaction, note that we should never run any code in a transaction block directly
+ await db.transaction(async (transactionalEntityManager) => {
+ await transactionalEntityManager.save(user);
+ await transactionalEntityManager.save(userKeypair);
+ await transactionalEntityManager.save(userProfile);
+ await transactionalEntityManager.save(usedUsername);
+ });
+
+ const account = await Users.findOneByOrFail({ id: user.id });
+
+ usersChart.update(account, true);
return { account, secret };
}
diff --git a/packages/backend/src/server/api/endpoints/admin/accounts/hosted.ts b/packages/backend/src/server/api/endpoints/admin/accounts/hosted.ts
index a7b6e95c2..a5423e154 100644
--- a/packages/backend/src/server/api/endpoints/admin/accounts/hosted.ts
+++ b/packages/backend/src/server/api/endpoints/admin/accounts/hosted.ts
@@ -3,6 +3,7 @@ import { Meta } from "@/models/entities/meta.js";
import { insertModerationLog } from "@/services/insert-moderation-log.js";
import { db } from "@/db/postgre.js";
import define from "../../../define.js";
+import { Metas } from "@/models/index.js";
export const meta = {
tags: ["admin"],
@@ -106,21 +107,19 @@ export default define(meta, paramDef, async (ps, me) => {
if (config.summalyProxyUrl !== undefined) {
set.summalyProxy = config.summalyProxyUrl;
}
- await db.transaction(async (transactionalEntityManager) => {
- const metas = await transactionalEntityManager.find(Meta, {
- order: {
- id: "DESC",
- },
- });
- const meta = metas[0];
-
- if (meta) {
- await transactionalEntityManager.update(Meta, meta.id, set);
- } else {
- await transactionalEntityManager.save(Meta, set);
- }
+ const meta = await Metas.findOne({
+ where: {},
+ order: {
+ id: "DESC",
+ },
});
+
+ if (meta)
+ await Metas.update(meta.id, set);
+ else
+ await Metas.save(set);
+
insertModerationLog(me, "updateMeta");
}
return hosted;
diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts
index 89f657ef4..e4f9b51a7 100644
--- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts
+++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts
@@ -2,6 +2,7 @@ import { Meta } from "@/models/entities/meta.js";
import { insertModerationLog } from "@/services/insert-moderation-log.js";
import { db } from "@/db/postgre.js";
import define from "../../define.js";
+import { Metas } from "@/models/index.js";
export const meta = {
tags: ["admin"],
@@ -546,21 +547,17 @@ export default define(meta, paramDef, async (ps, me) => {
}
}
- await db.transaction(async (transactionalEntityManager) => {
- const metas = await transactionalEntityManager.find(Meta, {
- order: {
- id: "DESC",
- },
- });
-
- const meta = metas[0];
-
- if (meta) {
- await transactionalEntityManager.update(Meta, meta.id, set);
- } else {
- await transactionalEntityManager.save(Meta, set);
- }
+ const meta = await Metas.findOne({
+ where: {},
+ order: {
+ id: "DESC",
+ },
});
+ if (meta)
+ await Metas.update(meta.id, set);
+ else
+ await Metas.save(set);
+
insertModerationLog(me, "updateMeta");
});
diff --git a/packages/backend/src/services/create-system-user.ts b/packages/backend/src/services/create-system-user.ts
index 24536090a..60901e186 100644
--- a/packages/backend/src/services/create-system-user.ts
+++ b/packages/backend/src/services/create-system-user.ts
@@ -9,8 +9,9 @@ import { UserKeypair } from "@/models/entities/user-keypair.js";
import { UsedUsername } from "@/models/entities/used-username.js";
import { db } from "@/db/postgre.js";
import { hashPassword } from "@/misc/password.js";
+import { Users } from "@/models/index.js";
-export async function createSystemUser(username: string) {
+export async function createSystemUser(username: string): Promise {
const password = uuid();
// Generate hash of password
@@ -23,49 +24,51 @@ export async function createSystemUser(username: string) {
let account!: User;
- // Start transaction
- await db.transaction(async (transactionalEntityManager) => {
- const exist = await transactionalEntityManager.findOneBy(User, {
- usernameLower: username.toLowerCase(),
- host: IsNull(),
- });
-
- if (exist) throw new Error("the user is already exists");
-
- account = await transactionalEntityManager
- .insert(User, {
- id: genId(),
- createdAt: new Date(),
- username: username,
- usernameLower: username.toLowerCase(),
- host: null,
- token: secret,
- isAdmin: false,
- isLocked: true,
- isExplorable: false,
- isBot: true,
- })
- .then((x) =>
- transactionalEntityManager.findOneByOrFail(User, x.identifiers[0]),
- );
-
- await transactionalEntityManager.insert(UserKeypair, {
- publicKey: keyPair.publicKey,
- privateKey: keyPair.privateKey,
- userId: account.id,
- });
-
- await transactionalEntityManager.insert(UserProfile, {
- userId: account.id,
- autoAcceptFollowed: false,
- password: hash,
- });
-
- await transactionalEntityManager.insert(UsedUsername, {
- createdAt: new Date(),
- username: username.toLowerCase(),
- });
+ const exist = await Users.findOneBy({
+ usernameLower: username.toLowerCase(),
+ host: IsNull(),
});
- return account;
+ if (exist) throw new Error("the user is already exists");
+
+ // Prepare objects
+ const user = {
+ id: genId(),
+ createdAt: new Date(),
+ username: username,
+ usernameLower: username.toLowerCase(),
+ host: null,
+ token: secret,
+ isAdmin: false,
+ isLocked: true,
+ isExplorable: false,
+ isBot: true,
+ };
+
+ const userKeypair = {
+ publicKey: keyPair.publicKey,
+ privateKey: keyPair.privateKey,
+ userId: user.id,
+ };
+
+ const userProfile = {
+ userId: user.id,
+ autoAcceptFollowed: false,
+ password: hash,
+ };
+
+ const usedUsername = {
+ createdAt: new Date(),
+ username: username.toLowerCase(),
+ }
+
+ // Save the objects atomically using a db transaction, note that we should never run any code in a transaction block directly
+ await db.transaction(async (transactionalEntityManager) => {
+ await transactionalEntityManager.insert(User, user);
+ await transactionalEntityManager.insert(UserKeypair, userKeypair);
+ await transactionalEntityManager.insert(UserProfile, userProfile);
+ await transactionalEntityManager.insert(UsedUsername, usedUsername);
+ });
+
+ return Users.findOneByOrFail({ id: user.id });
}
diff --git a/packages/backend/src/services/note/create.ts b/packages/backend/src/services/note/create.ts
index 9a549dfa2..c41897c34 100644
--- a/packages/backend/src/services/note/create.ts
+++ b/packages/backend/src/services/note/create.ts
@@ -756,30 +756,30 @@ async function insertNote(
// 投稿を作成
try {
if (insert.hasPoll) {
- // Start transaction
+ // Prepare objects
+ if (!data.poll) throw new Error("Empty poll data");
+
+ let expiresAt: Date | null;
+ if (!data.poll.expiresAt || isNaN(data.poll.expiresAt.getTime())) {
+ expiresAt = null;
+ } else {
+ expiresAt = data.poll.expiresAt;
+ }
+
+ const poll = new Poll({
+ noteId: insert.id,
+ choices: data.poll.choices,
+ expiresAt,
+ multiple: data.poll.multiple,
+ votes: new Array(data.poll.choices.length).fill(0),
+ noteVisibility: insert.visibility,
+ userId: user.id,
+ userHost: user.host,
+ });
+
+ // Save the objects atomically using a db transaction, note that we should never run any code in a transaction block directly
await db.transaction(async (transactionalEntityManager) => {
- if (!data.poll) throw new Error("Empty poll data");
-
await transactionalEntityManager.insert(Note, insert);
-
- let expiresAt: Date | null;
- if (!data.poll.expiresAt || isNaN(data.poll.expiresAt.getTime())) {
- expiresAt = null;
- } else {
- expiresAt = data.poll.expiresAt;
- }
-
- const poll = new Poll({
- noteId: insert.id,
- choices: data.poll.choices,
- expiresAt,
- multiple: data.poll.multiple,
- votes: new Array(data.poll.choices.length).fill(0),
- noteVisibility: insert.visibility,
- userId: user.id,
- userHost: user.host,
- });
-
await transactionalEntityManager.insert(Poll, poll);
});
} else {