diff --git a/bun.lockb b/bun.lockb index 2ac103e0..2fa0247f 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/database/entities/Status.ts b/database/entities/Status.ts index c82dad63..c41451f4 100644 --- a/database/entities/Status.ts +++ b/database/entities/Status.ts @@ -260,7 +260,7 @@ export const createNewStatus = async (data: { .join("\n"); } - let status = await client.status.create({ + const status = await client.status.create({ data: { authorId: data.account.id, applicationId: data.application?.id, @@ -290,12 +290,7 @@ export const createNewStatus = async (data: { quotingPostId: data.quote?.id, instanceId: data.account.instanceId || undefined, isReblog: false, - uri: - data.uri || - new URL( - `/statuses/FAKE-${crypto.randomUUID()}`, - config.http.base_url, - ).toString(), + uri: data.uri || null, mentions: { connect: mentions.map((mention) => { return { @@ -307,22 +302,6 @@ export const createNewStatus = async (data: { include: statusAndUserRelations, }); - // Update URI - status = await client.status.update({ - where: { - id: status.id, - }, - data: { - uri: - data.uri || - new URL( - `/statuses/${status.id}`, - config.http.base_url, - ).toString(), - }, - include: statusAndUserRelations, - }); - // Create notification if (status.inReplyToPost) { await client.notification.create({ @@ -503,9 +482,19 @@ export const statusToAPI = async ( sensitive: status.sensitive, spoiler_text: status.spoilerText, tags: [], - uri: new URL(`/statuses/${status.id}`, config.http.base_url).toString(), + uri: + status.uri || + new URL( + `/@${status.author.username}/${status.id}`, + config.http.base_url, + ).toString(), visibility: "public", - url: new URL(`/statuses/${status.id}`, config.http.base_url).toString(), + url: + status.uri || + new URL( + `/@${status.author.username}/${status.id}`, + config.http.base_url, + ).toString(), bookmarked: false, quote: status.quotingPost ? await statusToAPI( @@ -584,8 +573,8 @@ export const statusToLysand = (status: StatusWithRelations): Note => { // TODO: Add attachments attachments: [], is_sensitive: status.sensitive, - mentions: status.mentions.map((mention) => mention.uri), - quotes: status.quotingPost ? [status.quotingPost.uri] : [], + mentions: status.mentions.map((mention) => mention.uri || ""), + quotes: status.quotingPost ? [status.quotingPost.uri || ""] : [], replies_to: status.inReplyToPostId ? [status.inReplyToPostId] : [], subject: status.spoilerText, extensions: { diff --git a/database/entities/User.ts b/database/entities/User.ts index 69d438d6..a70155f7 100644 --- a/database/entities/User.ts +++ b/database/entities/User.ts @@ -262,7 +262,6 @@ export const createNewLocalUser = async (data: { avatar: data.avatar ?? config.defaults.avatar, header: data.header ?? config.defaults.avatar, isAdmin: data.admin ?? false, - uri: "", publicKey: keys.public_key, privateKey: keys.private_key, source: { @@ -273,20 +272,13 @@ export const createNewLocalUser = async (data: { fields: [], }, }, + include: userRelations, }); // Add to Meilisearch await addUserToMeilisearch(user); - return await client.user.update({ - where: { - id: user.id, - }, - data: { - uri: new URL(`/users/${user.id}`, config.http.base_url).toString(), - }, - include: userRelations, - }); + return user; }; /** @@ -408,7 +400,9 @@ export const userToAPI = ( username: user.username, display_name: user.displayName, note: user.note, - url: user.uri, + url: + user.uri || + new URL(`/@${user.username}`, config.http.base_url).toString(), avatar: getAvatarUrl(user, config), header: getHeaderUrl(user, config), locked: user.isLocked, @@ -458,7 +452,7 @@ export const userToLysand = (user: UserWithRelations): LysandUser => { return { id: user.id, type: "User", - uri: user.uri, + uri: user.uri || "", bio: [ { content: user.note, diff --git a/package.json b/package.json index 9377f7c4..d4df4e96 100644 --- a/package.json +++ b/package.json @@ -61,10 +61,12 @@ "@types/jsonld": "^1.5.13", "@typescript-eslint/eslint-plugin": "latest", "@unocss/cli": "latest", + "@unocss/transformer-directives": "^0.59.0", "@vitejs/plugin-vue": "latest", "@vueuse/head": "^2.0.0", "activitypub-types": "^1.0.3", "bun-types": "latest", + "shiki": "^1.2.4", "typescript": "latest", "unocss": "latest", "untyped": "^1.4.2", diff --git a/pages/pages/[username]/[uuid].vue b/pages/pages/[username]/[uuid].vue new file mode 100644 index 00000000..40b5e265 --- /dev/null +++ b/pages/pages/[username]/[uuid].vue @@ -0,0 +1,65 @@ + + + + + \ No newline at end of file diff --git a/pages/pages/[username]/index.vue b/pages/pages/[username]/index.vue new file mode 100644 index 00000000..78c07638 --- /dev/null +++ b/pages/pages/[username]/index.vue @@ -0,0 +1,82 @@ + + + + + \ No newline at end of file diff --git a/pages/pages/oauth/authorize.vue b/pages/pages/oauth/authorize.vue index 7c407f11..6a52823a 100644 --- a/pages/pages/oauth/authorize.vue +++ b/pages/pages/oauth/authorize.vue @@ -7,7 +7,7 @@
+ :action="`/api/auth/login?redirect_uri=${redirect_uri}&response_type=${response_type}&client_id=${client_id}&scope=${scope}`">

Login to your account

diff --git a/pages/pages/oauth/redirect.vue b/pages/pages/oauth/redirect.vue index 6543636f..99644a2d 100644 --- a/pages/pages/oauth/redirect.vue +++ b/pages/pages/oauth/redirect.vue @@ -7,7 +7,7 @@
+ :action="`/api/auth/redirect?redirect_uri=${encodeURIComponent(redirect_uri)}&client_id=${client_id}&code=${code}`">

Allow this application to access your account?

diff --git a/pages/routes.ts b/pages/routes.ts index 355b9236..c9278851 100644 --- a/pages/routes.ts +++ b/pages/routes.ts @@ -4,6 +4,8 @@ import authorizeVue from "./pages/oauth/authorize.vue"; import redirectVue from "./pages/oauth/redirect.vue"; import registerIndexVue from "./pages/register/index.vue"; import successVue from "./pages/register/success.vue"; +import statusVue from "./pages/[username]/[uuid].vue"; +import userVue from "./pages/[username]/index.vue"; export default [ { path: "/", component: indexVue }, @@ -11,4 +13,6 @@ export default [ { path: "/oauth/redirect", component: redirectVue }, { path: "/register", component: registerIndexVue }, { path: "/register/success", component: successVue }, + { path: "/:username/:uuid", component: statusVue }, + { path: "/:username", component: userVue }, ] as RouteRecordRaw[]; diff --git a/prisma/migrations/20240409042719_make_uri_nullable/migration.sql b/prisma/migrations/20240409042719_make_uri_nullable/migration.sql new file mode 100644 index 00000000..5b4b1d5e --- /dev/null +++ b/prisma/migrations/20240409042719_make_uri_nullable/migration.sql @@ -0,0 +1,5 @@ +-- AlterTable +ALTER TABLE "Status" ALTER COLUMN "uri" DROP NOT NULL; + +-- AlterTable +ALTER TABLE "User" ALTER COLUMN "uri" DROP NOT NULL; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 9e227845..4c5098b9 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -104,7 +104,7 @@ model Relationship { model Status { id String @id @default(dbgenerated("uuid_generate_v7()")) @db.Uuid - uri String @unique + uri String? @unique author User @relation("UserStatuses", fields: [authorId], references: [id], onDelete: Cascade) authorId String @db.Uuid createdAt DateTime @default(now()) @@ -228,7 +228,7 @@ model Notification { model User { id String @id @default(dbgenerated("uuid_generate_v7()")) @db.Uuid - uri String @unique + uri String? @unique username String @unique displayName String password String? // Nullable diff --git a/routes.ts b/routes.ts index f2837784..e2b1fece 100644 --- a/routes.ts +++ b/routes.ts @@ -33,8 +33,8 @@ export const rawRoutes = { "/api/v1/timelines/public": "./server/api/api/v1/timelines/public", "/api/v2/media": "./server/api/api/v2/media/index", "/api/v2/search": "./server/api/api/v2/search/index", - "/auth/login": "./server/api/auth/login/index", - "/auth/redirect": "./server/api/auth/redirect/index", + "/api/auth/login": "./server/api/api/auth/login/index", + "/api/auth/redirect": "./server/api/api/auth/redirect/index", "/nodeinfo/2.0": "./server/api/nodeinfo/2.0/index", "/oauth/authorize-external": "./server/api/oauth/authorize-external/index", "/oauth/providers": "./server/api/oauth/providers/index", diff --git a/server.ts b/server.ts index 5629fb10..556c6ef2 100644 --- a/server.ts +++ b/server.ts @@ -124,7 +124,7 @@ export const createServer = ( // Check for allowed requests // @ts-expect-error Stupid error - if (!meta.allowedMethods.includes(req.method as string)) { + if (!meta.allowedMethods.includes(req.method)) { return errorResponse( `Method not allowed: allowed methods are: ${meta.allowedMethods.join( ", ", @@ -137,17 +137,15 @@ export const createServer = ( const auth = await getFromRequest(req); // Check for authentication if required - if (meta.auth.required) { - if (!auth.user) { - return errorResponse("Unauthorized", 401); - } - } else if ( - // @ts-expect-error Stupid error - (meta.auth.requiredOnMethods ?? []).includes(req.method) + if ( + (meta.auth.required || + (meta.auth.requiredOnMethods ?? []).includes( + // @ts-expect-error Stupid error + req.method, + )) && + !auth.user ) { - if (!auth.user) { - return errorResponse("Unauthorized", 401); - } + return errorResponse("Unauthorized", 401); } let parsedRequest = {}; @@ -172,68 +170,53 @@ export const createServer = ( }, }); } - if (matchedRoute?.name === "/[...404]" || !matchedRoute) { - if (new URL(req.url).pathname.startsWith("/api")) { - return errorResponse("Route not found", 404); + + if (new URL(req.url).pathname.startsWith("/api")) { + return errorResponse("Route not found", 404); + } + + // Proxy response from Vite at localhost:5173 if in development mode + if (isProd) { + let file = Bun.file("./pages/dist/index.html"); + if (new URL(req.url).pathname.startsWith("/assets")) { + file = Bun.file(`./pages/dist${new URL(req.url).pathname}`); } - // Proxy response from Vite at localhost:5173 if in development mode - if (isProd) { - if (new URL(req.url).pathname.startsWith("/assets")) { - const file = Bun.file( - `./pages/dist${new URL(req.url).pathname}`, - ); - - // Serve from pages/dist/assets - if (await file.exists()) { - return clientResponse(file, 200, { - "Content-Type": file.type, - }); - } - return errorResponse("Asset not found", 404); - } - if (new URL(req.url).pathname.startsWith("/api")) { - return errorResponse("Route not found", 404); - } - - const file = Bun.file("./pages/dist/index.html"); - - // Serve from pages/dist + // Serve from pages/dist/assets + if (await file.exists()) { return clientResponse(file, 200, { "Content-Type": file.type, }); } - const proxy = await fetch( - req.url.replace( + return errorResponse("Asset not found", 404); + } + + const proxy = await fetch( + req.url.replace(config.http.base_url, "http://localhost:5173"), + ).catch(async (e) => { + await logger.logError( + LogLevel.ERROR, + "Server.Proxy", + e as Error, + ); + await logger.log( + LogLevel.ERROR, + "Server.Proxy", + `The development Vite server is not running or the route is not found: ${req.url.replace( config.http.base_url, "http://localhost:5173", - ), - ).catch(async (e) => { - await logger.logError( - LogLevel.ERROR, - "Server.Proxy", - e as Error, - ); - await logger.log( - LogLevel.ERROR, - "Server.Proxy", - `The development Vite server is not running or the route is not found: ${req.url.replace( - config.http.base_url, - "http://localhost:5173", - )}`, - ); - return errorResponse("Route not found", 404); - }); - - if ( - proxy.status !== 404 && - !(await proxy.clone().text()).includes("404 Not Found") - ) { - return proxy; - } - + )}`, + ); return errorResponse("Route not found", 404); + }); + + if ( + proxy.status !== 404 && + !(await proxy.clone().text()).includes("404 Not Found") + ) { + return proxy; } + return errorResponse("Route not found", 404); }, }); diff --git a/server/api/[...404].ts b/server/api/[...404].ts index 6036c6b9..7b9dbd4c 100644 --- a/server/api/[...404].ts +++ b/server/api/[...404].ts @@ -17,5 +17,5 @@ export const meta = applyConfig({ * Default catch-all route, returns a 404 error. */ export default apiRoute(() => { - return errorResponse("This API route does not exist", 404); + return errorResponse("Route not found", 404); }); diff --git a/server/api/auth/login/index.ts b/server/api/api/auth/login/index.ts similarity index 98% rename from server/api/auth/login/index.ts rename to server/api/api/auth/login/index.ts index d2059066..8e8f78b2 100644 --- a/server/api/auth/login/index.ts +++ b/server/api/api/auth/login/index.ts @@ -10,7 +10,7 @@ export const meta = applyConfig({ max: 4, duration: 60, }, - route: "/auth/login", + route: "/api/auth/login", auth: { required: false, }, diff --git a/server/api/auth/redirect/index.ts b/server/api/api/auth/redirect/index.ts similarity index 97% rename from server/api/auth/redirect/index.ts rename to server/api/api/auth/redirect/index.ts index 90e04a49..bfe7e912 100644 --- a/server/api/auth/redirect/index.ts +++ b/server/api/api/auth/redirect/index.ts @@ -8,7 +8,7 @@ export const meta = applyConfig({ max: 4, duration: 60, }, - route: "/auth/redirect", + route: "/api/auth/redirect", auth: { required: false, }, diff --git a/server/api/api/v1/accounts/[id]/index.ts b/server/api/api/v1/accounts/[id]/index.ts index 7d09ba25..7bc86b2b 100644 --- a/server/api/api/v1/accounts/[id]/index.ts +++ b/server/api/api/v1/accounts/[id]/index.ts @@ -13,7 +13,7 @@ export const meta = applyConfig({ }, route: "/api/v1/accounts/:id", auth: { - required: true, + required: false, oauthPermissions: [], }, }); diff --git a/server/api/api/v1/accounts/search/index.ts b/server/api/api/v1/accounts/search/index.ts index 1e3e810f..0f22a66d 100644 --- a/server/api/api/v1/accounts/search/index.ts +++ b/server/api/api/v1/accounts/search/index.ts @@ -12,7 +12,7 @@ export const meta = applyConfig({ duration: 60, }, auth: { - required: true, + required: false, oauthPermissions: ["read:accounts"], }, }); @@ -25,11 +25,6 @@ export default apiRoute<{ following?: boolean; }>(async (req, matchedRoute, extraData) => { // TODO: Add checks for disabled or not email verified accounts - - const { user } = extraData.auth; - - if (!user) return errorResponse("Unauthorized", 401); - const { following = false, limit = 40, @@ -37,6 +32,10 @@ export default apiRoute<{ q, } = extraData.parsedRequest; + const { user } = extraData.auth; + + if (!user && following) return errorResponse("Unauthorized", 401); + if (limit < 1 || limit > 80) { return errorResponse("Limit must be between 1 and 80", 400); } @@ -60,7 +59,7 @@ export default apiRoute<{ relationshipSubjects: following ? { some: { - ownerId: user.id, + ownerId: user?.id, following, }, } diff --git a/tests/api/accounts.test.ts b/tests/api/accounts.test.ts index c0293c25..0b9adb9a 100644 --- a/tests/api/accounts.test.ts +++ b/tests/api/accounts.test.ts @@ -172,7 +172,7 @@ describe("API Tests", () => { expect(account.statuses_count).toBe(0); expect(account.note).toBe(""); expect(account.url).toBe( - new URL(`/users/${user.id}`, config.http.base_url).toString(), + new URL(`/@${user.username}`, config.http.base_url).toString(), ); expect(account.avatar).toBeDefined(); expect(account.avatar_static).toBeDefined(); diff --git a/tests/oauth.test.ts b/tests/oauth.test.ts index 1911d637..aaf1da00 100644 --- a/tests/oauth.test.ts +++ b/tests/oauth.test.ts @@ -57,7 +57,7 @@ describe("POST /api/v1/apps/", () => { }); }); -describe("POST /auth/login/", () => { +describe("POST /api/auth/login/", () => { test("should get a code", async () => { const formData = new FormData(); @@ -67,7 +67,7 @@ describe("POST /auth/login/", () => { const response = await sendTestRequest( new Request( wrapRelativeUrl( - `/auth/login/?client_id=${client_id}&redirect_uri=https://example.com&response_type=code&scope=read+write`, + `/api/auth/login/?client_id=${client_id}&redirect_uri=https://example.com&response_type=code&scope=read+write`, base_url, ), { diff --git a/uno.config.ts b/uno.config.ts index e120f3d3..18f92167 100644 --- a/uno.config.ts +++ b/uno.config.ts @@ -1,4 +1,6 @@ import { presetForms } from "@julr/unocss-preset-forms"; +import transformerDirectives from "@unocss/transformer-directives"; + import { defineConfig, presetTypography, @@ -19,4 +21,5 @@ export default defineConfig({ presetWebFonts(), presetForms(), ], + transformers: [transformerDirectives()], });