i really need to get this outta here so i can work on the main site lol
1
.gitignore
vendored
|
@ -12,7 +12,6 @@ yarn-debug.log*
|
|||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
|
||||
# environment variables
|
||||
.env
|
||||
.env.production
|
||||
|
|
10
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"inlineChat.lineNaturalLanguageHint": false,
|
||||
"workbench.editor.empty.hint": "hidden",
|
||||
"github.copilot.enable": {
|
||||
"*": false,
|
||||
"plaintext": false,
|
||||
"markdown": false,
|
||||
"scminput": false
|
||||
}
|
||||
}
|
|
@ -23,6 +23,8 @@ const m2dxOptions = {
|
|||
export default defineConfig({
|
||||
site: "https://eleboog.com",
|
||||
|
||||
output: 'server',
|
||||
|
||||
vite: {
|
||||
ssr: {
|
||||
external: ['prismjs'],
|
||||
|
@ -52,8 +54,6 @@ export default defineConfig({
|
|||
rehypePlugins: [rehypeSlug, [rehypeMdxCodeProps, {tagName: 'code'}]],
|
||||
}), icon()],
|
||||
|
||||
output: 'server',
|
||||
|
||||
adapter: node({
|
||||
mode: 'standalone'
|
||||
})
|
||||
|
|
59
better-auth_migrations/2025-05-08T00-01-52.106Z.sql
Normal file
|
@ -0,0 +1,59 @@
|
|||
alter table "user" add column "emailVerified" boolean not null;
|
||||
|
||||
alter table "user" add column "createdAt" timestamp not null;
|
||||
|
||||
alter table "user" add column "updatedAt" timestamp not null;
|
||||
|
||||
alter table "user" add column "username" text unique;
|
||||
|
||||
alter table "user" add column "displayUsername" text;
|
||||
|
||||
alter table "user" add column "role" text;
|
||||
|
||||
alter table "user" add column "banned" boolean;
|
||||
|
||||
alter table "user" add column "banReason" text;
|
||||
|
||||
alter table "user" add column "banExpires" timestamp;
|
||||
|
||||
alter table "session" add column "expiresAt" timestamp not null;
|
||||
|
||||
alter table "session" add column "createdAt" timestamp not null;
|
||||
|
||||
alter table "session" add column "updatedAt" timestamp not null;
|
||||
|
||||
alter table "session" add column "ipAddress" text;
|
||||
|
||||
alter table "session" add column "userAgent" text;
|
||||
|
||||
alter table "session" add column "userId" text not null references "user" ("id");
|
||||
|
||||
alter table "session" add column "impersonatedBy" text;
|
||||
|
||||
alter table "account" add column "accountId" text not null;
|
||||
|
||||
alter table "account" add column "providerId" text not null;
|
||||
|
||||
alter table "account" add column "userId" text not null references "user" ("id");
|
||||
|
||||
alter table "account" add column "accessToken" text;
|
||||
|
||||
alter table "account" add column "refreshToken" text;
|
||||
|
||||
alter table "account" add column "idToken" text;
|
||||
|
||||
alter table "account" add column "accessTokenExpiresAt" timestamp;
|
||||
|
||||
alter table "account" add column "refreshTokenExpiresAt" timestamp;
|
||||
|
||||
alter table "account" add column "createdAt" timestamp not null;
|
||||
|
||||
alter table "account" add column "updatedAt" timestamp not null;
|
||||
|
||||
alter table "verification" add column "expiresAt" timestamp not null;
|
||||
|
||||
alter table "verification" add column "createdAt" timestamp;
|
||||
|
||||
alter table "verification" add column "updatedAt" timestamp;
|
||||
|
||||
create table "passkey" ("id" text not null primary key, "name" text, "publicKey" text not null, "userId" text not null references "user" ("id"), "credentialID" text not null, "counter" integer not null, "deviceType" text not null, "backedUp" boolean not null, "transports" text, "createdAt" timestamp);
|
1
better-auth_migrations/2025-05-08T00-05-07.154Z.sql
Normal file
|
@ -0,0 +1 @@
|
|||
;
|
9
better-auth_migrations/2025-05-08T00-08-30.399Z.sql
Normal file
|
@ -0,0 +1,9 @@
|
|||
create table "user" ("id" text not null primary key, "name" text not null, "email" text not null unique, "emailVerified" boolean not null, "image" text, "createdAt" timestamp not null, "updatedAt" timestamp not null, "username" text unique, "displayUsername" text, "role" text, "banned" boolean, "banReason" text, "banExpires" timestamp);
|
||||
|
||||
create table "session" ("id" text not null primary key, "expiresAt" timestamp not null, "token" text not null unique, "createdAt" timestamp not null, "updatedAt" timestamp not null, "ipAddress" text, "userAgent" text, "userId" text not null references "user" ("id"), "impersonatedBy" text);
|
||||
|
||||
create table "account" ("id" text not null primary key, "accountId" text not null, "providerId" text not null, "userId" text not null references "user" ("id"), "accessToken" text, "refreshToken" text, "idToken" text, "accessTokenExpiresAt" timestamp, "refreshTokenExpiresAt" timestamp, "scope" text, "password" text, "createdAt" timestamp not null, "updatedAt" timestamp not null);
|
||||
|
||||
create table "verification" ("id" text not null primary key, "identifier" text not null, "value" text not null, "expiresAt" timestamp not null, "createdAt" timestamp, "updatedAt" timestamp);
|
||||
|
||||
create table "passkey" ("id" text not null primary key, "name" text, "publicKey" text not null, "userId" text not null references "user" ("id"), "credentialID" text not null, "counter" integer not null, "deviceType" text not null, "backedUp" boolean not null, "transports" text, "createdAt" timestamp);
|
1
better-auth_migrations/2025-05-08T00-17-29.914Z.sql
Normal file
|
@ -0,0 +1 @@
|
|||
;
|
11
drizzle.config.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
import 'dotenv/config';
|
||||
import { defineConfig } from 'drizzle-kit';
|
||||
|
||||
export default defineConfig({
|
||||
out: './drizzle',
|
||||
schema: './src/db/schema.ts',
|
||||
dialect: 'postgresql',
|
||||
dbCredentials: {
|
||||
url: process.env.DATABASE_URL!,
|
||||
},
|
||||
});
|
1
generated/prisma/client.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
export * from "./index"
|
4
generated/prisma/client.js
Normal file
|
@ -0,0 +1,4 @@
|
|||
|
||||
/* !!! This is code generated by Prisma. Do not edit directly. !!!
|
||||
/* eslint-disable */
|
||||
module.exports = { ...require('.') }
|
1
generated/prisma/default.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
export * from "./index"
|
4
generated/prisma/default.js
Normal file
|
@ -0,0 +1,4 @@
|
|||
|
||||
/* !!! This is code generated by Prisma. Do not edit directly. !!!
|
||||
/* eslint-disable */
|
||||
module.exports = { ...require('.') }
|
1
generated/prisma/edge.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
export * from "./default"
|
256
generated/prisma/edge.js
Normal file
242
generated/prisma/index-browser.js
Normal file
|
@ -0,0 +1,242 @@
|
|||
|
||||
/* !!! This is code generated by Prisma. Do not edit directly. !!!
|
||||
/* eslint-disable */
|
||||
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
|
||||
const {
|
||||
Decimal,
|
||||
objectEnumValues,
|
||||
makeStrictEnum,
|
||||
Public,
|
||||
getRuntime,
|
||||
skip
|
||||
} = require('./runtime/index-browser.js')
|
||||
|
||||
|
||||
const Prisma = {}
|
||||
|
||||
exports.Prisma = Prisma
|
||||
exports.$Enums = {}
|
||||
|
||||
/**
|
||||
* Prisma Client JS version: 6.7.0
|
||||
* Query Engine version: 3cff47a7f5d65c3ea74883f1d736e41d68ce91ed
|
||||
*/
|
||||
Prisma.prismaVersion = {
|
||||
client: "6.7.0",
|
||||
engine: "3cff47a7f5d65c3ea74883f1d736e41d68ce91ed"
|
||||
}
|
||||
|
||||
Prisma.PrismaClientKnownRequestError = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`PrismaClientKnownRequestError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)};
|
||||
Prisma.PrismaClientUnknownRequestError = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`PrismaClientUnknownRequestError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.PrismaClientRustPanicError = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`PrismaClientRustPanicError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.PrismaClientInitializationError = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`PrismaClientInitializationError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.PrismaClientValidationError = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`PrismaClientValidationError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.Decimal = Decimal
|
||||
|
||||
/**
|
||||
* Re-export of sql-template-tag
|
||||
*/
|
||||
Prisma.sql = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`sqltag is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.empty = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`empty is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.join = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`join is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.raw = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`raw is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.validator = Public.validator
|
||||
|
||||
/**
|
||||
* Extensions
|
||||
*/
|
||||
Prisma.getExtensionContext = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`Extensions.getExtensionContext is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.defineExtension = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`Extensions.defineExtension is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
|
||||
/**
|
||||
* Shorthand utilities for JSON filtering
|
||||
*/
|
||||
Prisma.DbNull = objectEnumValues.instances.DbNull
|
||||
Prisma.JsonNull = objectEnumValues.instances.JsonNull
|
||||
Prisma.AnyNull = objectEnumValues.instances.AnyNull
|
||||
|
||||
Prisma.NullTypes = {
|
||||
DbNull: objectEnumValues.classes.DbNull,
|
||||
JsonNull: objectEnumValues.classes.JsonNull,
|
||||
AnyNull: objectEnumValues.classes.AnyNull
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Enums
|
||||
*/
|
||||
|
||||
exports.Prisma.TransactionIsolationLevel = makeStrictEnum({
|
||||
ReadUncommitted: 'ReadUncommitted',
|
||||
ReadCommitted: 'ReadCommitted',
|
||||
RepeatableRead: 'RepeatableRead',
|
||||
Serializable: 'Serializable'
|
||||
});
|
||||
|
||||
exports.Prisma.AccountScalarFieldEnum = {
|
||||
id: 'id',
|
||||
accountId: 'accountId',
|
||||
providerId: 'providerId',
|
||||
userId: 'userId',
|
||||
accessToken: 'accessToken',
|
||||
refreshToken: 'refreshToken',
|
||||
idToken: 'idToken',
|
||||
accessTokenExpiresAt: 'accessTokenExpiresAt',
|
||||
refreshTokenExpiresAt: 'refreshTokenExpiresAt',
|
||||
scope: 'scope',
|
||||
password: 'password',
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt'
|
||||
};
|
||||
|
||||
exports.Prisma.PasskeyScalarFieldEnum = {
|
||||
id: 'id',
|
||||
name: 'name',
|
||||
publicKey: 'publicKey',
|
||||
userId: 'userId',
|
||||
credentialID: 'credentialID',
|
||||
counter: 'counter',
|
||||
deviceType: 'deviceType',
|
||||
backedUp: 'backedUp',
|
||||
transports: 'transports',
|
||||
createdAt: 'createdAt'
|
||||
};
|
||||
|
||||
exports.Prisma.SessionScalarFieldEnum = {
|
||||
id: 'id',
|
||||
expiresAt: 'expiresAt',
|
||||
token: 'token',
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt',
|
||||
ipAddress: 'ipAddress',
|
||||
userAgent: 'userAgent',
|
||||
userId: 'userId',
|
||||
impersonatedBy: 'impersonatedBy'
|
||||
};
|
||||
|
||||
exports.Prisma.UserScalarFieldEnum = {
|
||||
id: 'id',
|
||||
name: 'name',
|
||||
email: 'email',
|
||||
emailVerified: 'emailVerified',
|
||||
image: 'image',
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt',
|
||||
username: 'username',
|
||||
displayUsername: 'displayUsername',
|
||||
role: 'role',
|
||||
banned: 'banned',
|
||||
banReason: 'banReason',
|
||||
banExpires: 'banExpires'
|
||||
};
|
||||
|
||||
exports.Prisma.VerificationScalarFieldEnum = {
|
||||
id: 'id',
|
||||
identifier: 'identifier',
|
||||
value: 'value',
|
||||
expiresAt: 'expiresAt',
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt'
|
||||
};
|
||||
|
||||
exports.Prisma.SortOrder = {
|
||||
asc: 'asc',
|
||||
desc: 'desc'
|
||||
};
|
||||
|
||||
exports.Prisma.QueryMode = {
|
||||
default: 'default',
|
||||
insensitive: 'insensitive'
|
||||
};
|
||||
|
||||
exports.Prisma.NullsOrder = {
|
||||
first: 'first',
|
||||
last: 'last'
|
||||
};
|
||||
|
||||
|
||||
exports.Prisma.ModelName = {
|
||||
account: 'account',
|
||||
passkey: 'passkey',
|
||||
session: 'session',
|
||||
user: 'user',
|
||||
verification: 'verification'
|
||||
};
|
||||
|
||||
/**
|
||||
* This is a stub Prisma Client that will error at runtime if called.
|
||||
*/
|
||||
class PrismaClient {
|
||||
constructor() {
|
||||
return new Proxy(this, {
|
||||
get(target, prop) {
|
||||
let message
|
||||
const runtime = getRuntime()
|
||||
if (runtime.isEdge) {
|
||||
message = `PrismaClient is not configured to run in ${runtime.prettyName}. In order to run Prisma Client on edge runtime, either:
|
||||
- Use Prisma Accelerate: https://pris.ly/d/accelerate
|
||||
- Use Driver Adapters: https://pris.ly/d/driver-adapters
|
||||
`;
|
||||
} else {
|
||||
message = 'PrismaClient is unable to run in this browser environment, or has been bundled for the browser (running in `' + runtime.prettyName + '`).'
|
||||
}
|
||||
|
||||
message += `
|
||||
If this is unexpected, please open an issue: https://pris.ly/prisma-prisma-bug-report`
|
||||
|
||||
throw new Error(message)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
exports.PrismaClient = PrismaClient
|
||||
|
||||
Object.assign(exports, Prisma)
|
9458
generated/prisma/index.d.ts
vendored
Normal file
277
generated/prisma/index.js
Normal file
BIN
generated/prisma/libquery_engine-darwin-arm64.dylib.node
Executable file
140
generated/prisma/package.json
Normal file
|
@ -0,0 +1,140 @@
|
|||
{
|
||||
"name": "prisma-client-d2892a00f3a6572ec0e53630203b0cd2844b8ae4e3b7e0dcd9321aeee4f0ca5e",
|
||||
"main": "index.js",
|
||||
"types": "index.d.ts",
|
||||
"browser": "index-browser.js",
|
||||
"exports": {
|
||||
"./client": {
|
||||
"require": {
|
||||
"node": "./index.js",
|
||||
"edge-light": "./wasm.js",
|
||||
"workerd": "./wasm.js",
|
||||
"worker": "./wasm.js",
|
||||
"browser": "./index-browser.js",
|
||||
"default": "./index.js"
|
||||
},
|
||||
"import": {
|
||||
"node": "./index.js",
|
||||
"edge-light": "./wasm.js",
|
||||
"workerd": "./wasm.js",
|
||||
"worker": "./wasm.js",
|
||||
"browser": "./index-browser.js",
|
||||
"default": "./index.js"
|
||||
},
|
||||
"default": "./index.js"
|
||||
},
|
||||
"./package.json": "./package.json",
|
||||
".": {
|
||||
"require": {
|
||||
"node": "./index.js",
|
||||
"edge-light": "./wasm.js",
|
||||
"workerd": "./wasm.js",
|
||||
"worker": "./wasm.js",
|
||||
"browser": "./index-browser.js",
|
||||
"default": "./index.js"
|
||||
},
|
||||
"import": {
|
||||
"node": "./index.js",
|
||||
"edge-light": "./wasm.js",
|
||||
"workerd": "./wasm.js",
|
||||
"worker": "./wasm.js",
|
||||
"browser": "./index-browser.js",
|
||||
"default": "./index.js"
|
||||
},
|
||||
"default": "./index.js"
|
||||
},
|
||||
"./edge": {
|
||||
"types": "./edge.d.ts",
|
||||
"require": "./edge.js",
|
||||
"import": "./edge.js",
|
||||
"default": "./edge.js"
|
||||
},
|
||||
"./react-native": {
|
||||
"types": "./react-native.d.ts",
|
||||
"require": "./react-native.js",
|
||||
"import": "./react-native.js",
|
||||
"default": "./react-native.js"
|
||||
},
|
||||
"./extension": {
|
||||
"types": "./extension.d.ts",
|
||||
"require": "./extension.js",
|
||||
"import": "./extension.js",
|
||||
"default": "./extension.js"
|
||||
},
|
||||
"./index-browser": {
|
||||
"types": "./index.d.ts",
|
||||
"require": "./index-browser.js",
|
||||
"import": "./index-browser.js",
|
||||
"default": "./index-browser.js"
|
||||
},
|
||||
"./index": {
|
||||
"types": "./index.d.ts",
|
||||
"require": "./index.js",
|
||||
"import": "./index.js",
|
||||
"default": "./index.js"
|
||||
},
|
||||
"./wasm": {
|
||||
"types": "./wasm.d.ts",
|
||||
"require": "./wasm.js",
|
||||
"import": "./wasm.mjs",
|
||||
"default": "./wasm.mjs"
|
||||
},
|
||||
"./runtime/client": {
|
||||
"types": "./runtime/client.d.ts",
|
||||
"require": "./runtime/client.js",
|
||||
"import": "./runtime/client.mjs",
|
||||
"default": "./runtime/client.mjs"
|
||||
},
|
||||
"./runtime/library": {
|
||||
"types": "./runtime/library.d.ts",
|
||||
"require": "./runtime/library.js",
|
||||
"import": "./runtime/library.mjs",
|
||||
"default": "./runtime/library.mjs"
|
||||
},
|
||||
"./runtime/binary": {
|
||||
"types": "./runtime/binary.d.ts",
|
||||
"require": "./runtime/binary.js",
|
||||
"import": "./runtime/binary.mjs",
|
||||
"default": "./runtime/binary.mjs"
|
||||
},
|
||||
"./runtime/wasm": {
|
||||
"types": "./runtime/wasm.d.ts",
|
||||
"require": "./runtime/wasm.js",
|
||||
"import": "./runtime/wasm.mjs",
|
||||
"default": "./runtime/wasm.mjs"
|
||||
},
|
||||
"./runtime/edge": {
|
||||
"types": "./runtime/edge.d.ts",
|
||||
"require": "./runtime/edge.js",
|
||||
"import": "./runtime/edge-esm.js",
|
||||
"default": "./runtime/edge-esm.js"
|
||||
},
|
||||
"./runtime/react-native": {
|
||||
"types": "./runtime/react-native.d.ts",
|
||||
"require": "./runtime/react-native.js",
|
||||
"import": "./runtime/react-native.js",
|
||||
"default": "./runtime/react-native.js"
|
||||
},
|
||||
"./generator-build": {
|
||||
"require": "./generator-build/index.js",
|
||||
"import": "./generator-build/index.js",
|
||||
"default": "./generator-build/index.js"
|
||||
},
|
||||
"./sql": {
|
||||
"require": {
|
||||
"types": "./sql.d.ts",
|
||||
"node": "./sql.js",
|
||||
"default": "./sql.js"
|
||||
},
|
||||
"import": {
|
||||
"types": "./sql.d.ts",
|
||||
"node": "./sql.mjs",
|
||||
"default": "./sql.mjs"
|
||||
},
|
||||
"default": "./sql.js"
|
||||
},
|
||||
"./*": "./*"
|
||||
},
|
||||
"version": "6.7.0",
|
||||
"sideEffects": false
|
||||
}
|
34
generated/prisma/runtime/edge-esm.js
Normal file
34
generated/prisma/runtime/edge.js
Normal file
370
generated/prisma/runtime/index-browser.d.ts
vendored
Normal file
|
@ -0,0 +1,370 @@
|
|||
declare class AnyNull extends NullTypesEnumValue {
|
||||
#private;
|
||||
}
|
||||
|
||||
declare type Args<T, F extends Operation> = T extends {
|
||||
[K: symbol]: {
|
||||
types: {
|
||||
operations: {
|
||||
[K in F]: {
|
||||
args: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
} ? T[symbol]['types']['operations'][F]['args'] : any;
|
||||
|
||||
declare class DbNull extends NullTypesEnumValue {
|
||||
#private;
|
||||
}
|
||||
|
||||
export declare function Decimal(n: Decimal.Value): Decimal;
|
||||
|
||||
export declare namespace Decimal {
|
||||
export type Constructor = typeof Decimal;
|
||||
export type Instance = Decimal;
|
||||
export type Rounding = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;
|
||||
export type Modulo = Rounding | 9;
|
||||
export type Value = string | number | Decimal;
|
||||
|
||||
// http://mikemcl.github.io/decimal.js/#constructor-properties
|
||||
export interface Config {
|
||||
precision?: number;
|
||||
rounding?: Rounding;
|
||||
toExpNeg?: number;
|
||||
toExpPos?: number;
|
||||
minE?: number;
|
||||
maxE?: number;
|
||||
crypto?: boolean;
|
||||
modulo?: Modulo;
|
||||
defaults?: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
export declare class Decimal {
|
||||
readonly d: number[];
|
||||
readonly e: number;
|
||||
readonly s: number;
|
||||
|
||||
constructor(n: Decimal.Value);
|
||||
|
||||
absoluteValue(): Decimal;
|
||||
abs(): Decimal;
|
||||
|
||||
ceil(): Decimal;
|
||||
|
||||
clampedTo(min: Decimal.Value, max: Decimal.Value): Decimal;
|
||||
clamp(min: Decimal.Value, max: Decimal.Value): Decimal;
|
||||
|
||||
comparedTo(n: Decimal.Value): number;
|
||||
cmp(n: Decimal.Value): number;
|
||||
|
||||
cosine(): Decimal;
|
||||
cos(): Decimal;
|
||||
|
||||
cubeRoot(): Decimal;
|
||||
cbrt(): Decimal;
|
||||
|
||||
decimalPlaces(): number;
|
||||
dp(): number;
|
||||
|
||||
dividedBy(n: Decimal.Value): Decimal;
|
||||
div(n: Decimal.Value): Decimal;
|
||||
|
||||
dividedToIntegerBy(n: Decimal.Value): Decimal;
|
||||
divToInt(n: Decimal.Value): Decimal;
|
||||
|
||||
equals(n: Decimal.Value): boolean;
|
||||
eq(n: Decimal.Value): boolean;
|
||||
|
||||
floor(): Decimal;
|
||||
|
||||
greaterThan(n: Decimal.Value): boolean;
|
||||
gt(n: Decimal.Value): boolean;
|
||||
|
||||
greaterThanOrEqualTo(n: Decimal.Value): boolean;
|
||||
gte(n: Decimal.Value): boolean;
|
||||
|
||||
hyperbolicCosine(): Decimal;
|
||||
cosh(): Decimal;
|
||||
|
||||
hyperbolicSine(): Decimal;
|
||||
sinh(): Decimal;
|
||||
|
||||
hyperbolicTangent(): Decimal;
|
||||
tanh(): Decimal;
|
||||
|
||||
inverseCosine(): Decimal;
|
||||
acos(): Decimal;
|
||||
|
||||
inverseHyperbolicCosine(): Decimal;
|
||||
acosh(): Decimal;
|
||||
|
||||
inverseHyperbolicSine(): Decimal;
|
||||
asinh(): Decimal;
|
||||
|
||||
inverseHyperbolicTangent(): Decimal;
|
||||
atanh(): Decimal;
|
||||
|
||||
inverseSine(): Decimal;
|
||||
asin(): Decimal;
|
||||
|
||||
inverseTangent(): Decimal;
|
||||
atan(): Decimal;
|
||||
|
||||
isFinite(): boolean;
|
||||
|
||||
isInteger(): boolean;
|
||||
isInt(): boolean;
|
||||
|
||||
isNaN(): boolean;
|
||||
|
||||
isNegative(): boolean;
|
||||
isNeg(): boolean;
|
||||
|
||||
isPositive(): boolean;
|
||||
isPos(): boolean;
|
||||
|
||||
isZero(): boolean;
|
||||
|
||||
lessThan(n: Decimal.Value): boolean;
|
||||
lt(n: Decimal.Value): boolean;
|
||||
|
||||
lessThanOrEqualTo(n: Decimal.Value): boolean;
|
||||
lte(n: Decimal.Value): boolean;
|
||||
|
||||
logarithm(n?: Decimal.Value): Decimal;
|
||||
log(n?: Decimal.Value): Decimal;
|
||||
|
||||
minus(n: Decimal.Value): Decimal;
|
||||
sub(n: Decimal.Value): Decimal;
|
||||
|
||||
modulo(n: Decimal.Value): Decimal;
|
||||
mod(n: Decimal.Value): Decimal;
|
||||
|
||||
naturalExponential(): Decimal;
|
||||
exp(): Decimal;
|
||||
|
||||
naturalLogarithm(): Decimal;
|
||||
ln(): Decimal;
|
||||
|
||||
negated(): Decimal;
|
||||
neg(): Decimal;
|
||||
|
||||
plus(n: Decimal.Value): Decimal;
|
||||
add(n: Decimal.Value): Decimal;
|
||||
|
||||
precision(includeZeros?: boolean): number;
|
||||
sd(includeZeros?: boolean): number;
|
||||
|
||||
round(): Decimal;
|
||||
|
||||
sine() : Decimal;
|
||||
sin() : Decimal;
|
||||
|
||||
squareRoot(): Decimal;
|
||||
sqrt(): Decimal;
|
||||
|
||||
tangent() : Decimal;
|
||||
tan() : Decimal;
|
||||
|
||||
times(n: Decimal.Value): Decimal;
|
||||
mul(n: Decimal.Value) : Decimal;
|
||||
|
||||
toBinary(significantDigits?: number): string;
|
||||
toBinary(significantDigits: number, rounding: Decimal.Rounding): string;
|
||||
|
||||
toDecimalPlaces(decimalPlaces?: number): Decimal;
|
||||
toDecimalPlaces(decimalPlaces: number, rounding: Decimal.Rounding): Decimal;
|
||||
toDP(decimalPlaces?: number): Decimal;
|
||||
toDP(decimalPlaces: number, rounding: Decimal.Rounding): Decimal;
|
||||
|
||||
toExponential(decimalPlaces?: number): string;
|
||||
toExponential(decimalPlaces: number, rounding: Decimal.Rounding): string;
|
||||
|
||||
toFixed(decimalPlaces?: number): string;
|
||||
toFixed(decimalPlaces: number, rounding: Decimal.Rounding): string;
|
||||
|
||||
toFraction(max_denominator?: Decimal.Value): Decimal[];
|
||||
|
||||
toHexadecimal(significantDigits?: number): string;
|
||||
toHexadecimal(significantDigits: number, rounding: Decimal.Rounding): string;
|
||||
toHex(significantDigits?: number): string;
|
||||
toHex(significantDigits: number, rounding?: Decimal.Rounding): string;
|
||||
|
||||
toJSON(): string;
|
||||
|
||||
toNearest(n: Decimal.Value, rounding?: Decimal.Rounding): Decimal;
|
||||
|
||||
toNumber(): number;
|
||||
|
||||
toOctal(significantDigits?: number): string;
|
||||
toOctal(significantDigits: number, rounding: Decimal.Rounding): string;
|
||||
|
||||
toPower(n: Decimal.Value): Decimal;
|
||||
pow(n: Decimal.Value): Decimal;
|
||||
|
||||
toPrecision(significantDigits?: number): string;
|
||||
toPrecision(significantDigits: number, rounding: Decimal.Rounding): string;
|
||||
|
||||
toSignificantDigits(significantDigits?: number): Decimal;
|
||||
toSignificantDigits(significantDigits: number, rounding: Decimal.Rounding): Decimal;
|
||||
toSD(significantDigits?: number): Decimal;
|
||||
toSD(significantDigits: number, rounding: Decimal.Rounding): Decimal;
|
||||
|
||||
toString(): string;
|
||||
|
||||
truncated(): Decimal;
|
||||
trunc(): Decimal;
|
||||
|
||||
valueOf(): string;
|
||||
|
||||
static abs(n: Decimal.Value): Decimal;
|
||||
static acos(n: Decimal.Value): Decimal;
|
||||
static acosh(n: Decimal.Value): Decimal;
|
||||
static add(x: Decimal.Value, y: Decimal.Value): Decimal;
|
||||
static asin(n: Decimal.Value): Decimal;
|
||||
static asinh(n: Decimal.Value): Decimal;
|
||||
static atan(n: Decimal.Value): Decimal;
|
||||
static atanh(n: Decimal.Value): Decimal;
|
||||
static atan2(y: Decimal.Value, x: Decimal.Value): Decimal;
|
||||
static cbrt(n: Decimal.Value): Decimal;
|
||||
static ceil(n: Decimal.Value): Decimal;
|
||||
static clamp(n: Decimal.Value, min: Decimal.Value, max: Decimal.Value): Decimal;
|
||||
static clone(object?: Decimal.Config): Decimal.Constructor;
|
||||
static config(object: Decimal.Config): Decimal.Constructor;
|
||||
static cos(n: Decimal.Value): Decimal;
|
||||
static cosh(n: Decimal.Value): Decimal;
|
||||
static div(x: Decimal.Value, y: Decimal.Value): Decimal;
|
||||
static exp(n: Decimal.Value): Decimal;
|
||||
static floor(n: Decimal.Value): Decimal;
|
||||
static hypot(...n: Decimal.Value[]): Decimal;
|
||||
static isDecimal(object: any): object is Decimal;
|
||||
static ln(n: Decimal.Value): Decimal;
|
||||
static log(n: Decimal.Value, base?: Decimal.Value): Decimal;
|
||||
static log2(n: Decimal.Value): Decimal;
|
||||
static log10(n: Decimal.Value): Decimal;
|
||||
static max(...n: Decimal.Value[]): Decimal;
|
||||
static min(...n: Decimal.Value[]): Decimal;
|
||||
static mod(x: Decimal.Value, y: Decimal.Value): Decimal;
|
||||
static mul(x: Decimal.Value, y: Decimal.Value): Decimal;
|
||||
static noConflict(): Decimal.Constructor; // Browser only
|
||||
static pow(base: Decimal.Value, exponent: Decimal.Value): Decimal;
|
||||
static random(significantDigits?: number): Decimal;
|
||||
static round(n: Decimal.Value): Decimal;
|
||||
static set(object: Decimal.Config): Decimal.Constructor;
|
||||
static sign(n: Decimal.Value): number;
|
||||
static sin(n: Decimal.Value): Decimal;
|
||||
static sinh(n: Decimal.Value): Decimal;
|
||||
static sqrt(n: Decimal.Value): Decimal;
|
||||
static sub(x: Decimal.Value, y: Decimal.Value): Decimal;
|
||||
static sum(...n: Decimal.Value[]): Decimal;
|
||||
static tan(n: Decimal.Value): Decimal;
|
||||
static tanh(n: Decimal.Value): Decimal;
|
||||
static trunc(n: Decimal.Value): Decimal;
|
||||
|
||||
static readonly default?: Decimal.Constructor;
|
||||
static readonly Decimal?: Decimal.Constructor;
|
||||
|
||||
static readonly precision: number;
|
||||
static readonly rounding: Decimal.Rounding;
|
||||
static readonly toExpNeg: number;
|
||||
static readonly toExpPos: number;
|
||||
static readonly minE: number;
|
||||
static readonly maxE: number;
|
||||
static readonly crypto: boolean;
|
||||
static readonly modulo: Decimal.Modulo;
|
||||
|
||||
static readonly ROUND_UP: 0;
|
||||
static readonly ROUND_DOWN: 1;
|
||||
static readonly ROUND_CEIL: 2;
|
||||
static readonly ROUND_FLOOR: 3;
|
||||
static readonly ROUND_HALF_UP: 4;
|
||||
static readonly ROUND_HALF_DOWN: 5;
|
||||
static readonly ROUND_HALF_EVEN: 6;
|
||||
static readonly ROUND_HALF_CEIL: 7;
|
||||
static readonly ROUND_HALF_FLOOR: 8;
|
||||
static readonly EUCLID: 9;
|
||||
}
|
||||
|
||||
declare type Exact<A, W> = (A extends unknown ? (W extends A ? {
|
||||
[K in keyof A]: Exact<A[K], W[K]>;
|
||||
} : W) : never) | (A extends Narrowable ? A : never);
|
||||
|
||||
export declare function getRuntime(): GetRuntimeOutput;
|
||||
|
||||
declare type GetRuntimeOutput = {
|
||||
id: RuntimeName;
|
||||
prettyName: string;
|
||||
isEdge: boolean;
|
||||
};
|
||||
|
||||
declare class JsonNull extends NullTypesEnumValue {
|
||||
#private;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates more strict variant of an enum which, unlike regular enum,
|
||||
* throws on non-existing property access. This can be useful in following situations:
|
||||
* - we have an API, that accepts both `undefined` and `SomeEnumType` as an input
|
||||
* - enum values are generated dynamically from DMMF.
|
||||
*
|
||||
* In that case, if using normal enums and no compile-time typechecking, using non-existing property
|
||||
* will result in `undefined` value being used, which will be accepted. Using strict enum
|
||||
* in this case will help to have a runtime exception, telling you that you are probably doing something wrong.
|
||||
*
|
||||
* Note: if you need to check for existence of a value in the enum you can still use either
|
||||
* `in` operator or `hasOwnProperty` function.
|
||||
*
|
||||
* @param definition
|
||||
* @returns
|
||||
*/
|
||||
export declare function makeStrictEnum<T extends Record<PropertyKey, string | number>>(definition: T): T;
|
||||
|
||||
declare type Narrowable = string | number | bigint | boolean | [];
|
||||
|
||||
declare class NullTypesEnumValue extends ObjectEnumValue {
|
||||
_getNamespace(): string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class for unique values of object-valued enums.
|
||||
*/
|
||||
declare abstract class ObjectEnumValue {
|
||||
constructor(arg?: symbol);
|
||||
abstract _getNamespace(): string;
|
||||
_getName(): string;
|
||||
toString(): string;
|
||||
}
|
||||
|
||||
export declare const objectEnumValues: {
|
||||
classes: {
|
||||
DbNull: typeof DbNull;
|
||||
JsonNull: typeof JsonNull;
|
||||
AnyNull: typeof AnyNull;
|
||||
};
|
||||
instances: {
|
||||
DbNull: DbNull;
|
||||
JsonNull: JsonNull;
|
||||
AnyNull: AnyNull;
|
||||
};
|
||||
};
|
||||
|
||||
declare type Operation = 'findFirst' | 'findFirstOrThrow' | 'findUnique' | 'findUniqueOrThrow' | 'findMany' | 'create' | 'createMany' | 'createManyAndReturn' | 'update' | 'updateMany' | 'updateManyAndReturn' | 'upsert' | 'delete' | 'deleteMany' | 'aggregate' | 'count' | 'groupBy' | '$queryRaw' | '$executeRaw' | '$queryRawUnsafe' | '$executeRawUnsafe' | 'findRaw' | 'aggregateRaw' | '$runCommandRaw';
|
||||
|
||||
declare namespace Public {
|
||||
export {
|
||||
validator
|
||||
}
|
||||
}
|
||||
export { Public }
|
||||
|
||||
declare type RuntimeName = 'workerd' | 'deno' | 'netlify' | 'node' | 'bun' | 'edge-light' | '';
|
||||
|
||||
declare function validator<V>(): <S>(select: Exact<S, V>) => S;
|
||||
|
||||
declare function validator<C, M extends Exclude<keyof C, `$${string}`>, O extends keyof C[M] & Operation>(client: C, model: M, operation: O): <S>(select: Exact<S, Args<C[M], O>>) => S;
|
||||
|
||||
declare function validator<C, M extends Exclude<keyof C, `$${string}`>, O extends keyof C[M] & Operation, P extends keyof Args<C[M], O>>(client: C, model: M, operation: O, prop: P): <S>(select: Exact<S, Args<C[M], O>[P]>) => S;
|
||||
|
||||
export { }
|
16
generated/prisma/runtime/index-browser.js
Normal file
3647
generated/prisma/runtime/library.d.ts
vendored
Normal file
146
generated/prisma/runtime/library.js
Normal file
83
generated/prisma/runtime/react-native.js
vendored
Normal file
35
generated/prisma/runtime/wasm.js
Normal file
82
generated/prisma/schema.prisma
Normal file
|
@ -0,0 +1,82 @@
|
|||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
output = "../generated/prisma"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
directUrl = env("DIRECT_URL")
|
||||
}
|
||||
|
||||
model account {
|
||||
id String @id
|
||||
accountId String
|
||||
providerId String
|
||||
userId String
|
||||
accessToken String?
|
||||
refreshToken String?
|
||||
idToken String?
|
||||
accessTokenExpiresAt DateTime? @db.Timestamp(6)
|
||||
refreshTokenExpiresAt DateTime? @db.Timestamp(6)
|
||||
scope String?
|
||||
password String?
|
||||
createdAt DateTime @db.Timestamp(6)
|
||||
updatedAt DateTime @db.Timestamp(6)
|
||||
user user @relation(fields: [userId], references: [id], onDelete: NoAction, onUpdate: NoAction)
|
||||
}
|
||||
|
||||
model passkey {
|
||||
id String @id
|
||||
name String?
|
||||
publicKey String
|
||||
userId String
|
||||
credentialID String
|
||||
counter Int
|
||||
deviceType String
|
||||
backedUp Boolean
|
||||
transports String?
|
||||
createdAt DateTime? @db.Timestamp(6)
|
||||
user user @relation(fields: [userId], references: [id], onDelete: NoAction, onUpdate: NoAction)
|
||||
}
|
||||
|
||||
model session {
|
||||
id String @id
|
||||
expiresAt DateTime @db.Timestamp(6)
|
||||
token String @unique
|
||||
createdAt DateTime @db.Timestamp(6)
|
||||
updatedAt DateTime @db.Timestamp(6)
|
||||
ipAddress String?
|
||||
userAgent String?
|
||||
userId String
|
||||
impersonatedBy String?
|
||||
user user @relation(fields: [userId], references: [id], onDelete: NoAction, onUpdate: NoAction)
|
||||
}
|
||||
|
||||
model user {
|
||||
id String @id
|
||||
name String
|
||||
email String @unique
|
||||
emailVerified Boolean
|
||||
image String?
|
||||
createdAt DateTime @db.Timestamp(6)
|
||||
updatedAt DateTime @db.Timestamp(6)
|
||||
username String? @unique
|
||||
displayUsername String?
|
||||
role String?
|
||||
banned Boolean?
|
||||
banReason String?
|
||||
banExpires DateTime? @db.Timestamp(6)
|
||||
account account[]
|
||||
passkey passkey[]
|
||||
session session[]
|
||||
}
|
||||
|
||||
model verification {
|
||||
id String @id
|
||||
identifier String
|
||||
value String
|
||||
expiresAt DateTime @db.Timestamp(6)
|
||||
createdAt DateTime? @db.Timestamp(6)
|
||||
updatedAt DateTime? @db.Timestamp(6)
|
||||
}
|
1
generated/prisma/wasm.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
export * from "./index"
|
242
generated/prisma/wasm.js
Normal file
|
@ -0,0 +1,242 @@
|
|||
|
||||
/* !!! This is code generated by Prisma. Do not edit directly. !!!
|
||||
/* eslint-disable */
|
||||
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
|
||||
const {
|
||||
Decimal,
|
||||
objectEnumValues,
|
||||
makeStrictEnum,
|
||||
Public,
|
||||
getRuntime,
|
||||
skip
|
||||
} = require('./runtime/index-browser.js')
|
||||
|
||||
|
||||
const Prisma = {}
|
||||
|
||||
exports.Prisma = Prisma
|
||||
exports.$Enums = {}
|
||||
|
||||
/**
|
||||
* Prisma Client JS version: 6.7.0
|
||||
* Query Engine version: 3cff47a7f5d65c3ea74883f1d736e41d68ce91ed
|
||||
*/
|
||||
Prisma.prismaVersion = {
|
||||
client: "6.7.0",
|
||||
engine: "3cff47a7f5d65c3ea74883f1d736e41d68ce91ed"
|
||||
}
|
||||
|
||||
Prisma.PrismaClientKnownRequestError = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`PrismaClientKnownRequestError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)};
|
||||
Prisma.PrismaClientUnknownRequestError = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`PrismaClientUnknownRequestError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.PrismaClientRustPanicError = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`PrismaClientRustPanicError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.PrismaClientInitializationError = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`PrismaClientInitializationError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.PrismaClientValidationError = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`PrismaClientValidationError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.Decimal = Decimal
|
||||
|
||||
/**
|
||||
* Re-export of sql-template-tag
|
||||
*/
|
||||
Prisma.sql = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`sqltag is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.empty = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`empty is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.join = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`join is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.raw = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`raw is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.validator = Public.validator
|
||||
|
||||
/**
|
||||
* Extensions
|
||||
*/
|
||||
Prisma.getExtensionContext = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`Extensions.getExtensionContext is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.defineExtension = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`Extensions.defineExtension is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
|
||||
/**
|
||||
* Shorthand utilities for JSON filtering
|
||||
*/
|
||||
Prisma.DbNull = objectEnumValues.instances.DbNull
|
||||
Prisma.JsonNull = objectEnumValues.instances.JsonNull
|
||||
Prisma.AnyNull = objectEnumValues.instances.AnyNull
|
||||
|
||||
Prisma.NullTypes = {
|
||||
DbNull: objectEnumValues.classes.DbNull,
|
||||
JsonNull: objectEnumValues.classes.JsonNull,
|
||||
AnyNull: objectEnumValues.classes.AnyNull
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Enums
|
||||
*/
|
||||
|
||||
exports.Prisma.TransactionIsolationLevel = makeStrictEnum({
|
||||
ReadUncommitted: 'ReadUncommitted',
|
||||
ReadCommitted: 'ReadCommitted',
|
||||
RepeatableRead: 'RepeatableRead',
|
||||
Serializable: 'Serializable'
|
||||
});
|
||||
|
||||
exports.Prisma.AccountScalarFieldEnum = {
|
||||
id: 'id',
|
||||
accountId: 'accountId',
|
||||
providerId: 'providerId',
|
||||
userId: 'userId',
|
||||
accessToken: 'accessToken',
|
||||
refreshToken: 'refreshToken',
|
||||
idToken: 'idToken',
|
||||
accessTokenExpiresAt: 'accessTokenExpiresAt',
|
||||
refreshTokenExpiresAt: 'refreshTokenExpiresAt',
|
||||
scope: 'scope',
|
||||
password: 'password',
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt'
|
||||
};
|
||||
|
||||
exports.Prisma.PasskeyScalarFieldEnum = {
|
||||
id: 'id',
|
||||
name: 'name',
|
||||
publicKey: 'publicKey',
|
||||
userId: 'userId',
|
||||
credentialID: 'credentialID',
|
||||
counter: 'counter',
|
||||
deviceType: 'deviceType',
|
||||
backedUp: 'backedUp',
|
||||
transports: 'transports',
|
||||
createdAt: 'createdAt'
|
||||
};
|
||||
|
||||
exports.Prisma.SessionScalarFieldEnum = {
|
||||
id: 'id',
|
||||
expiresAt: 'expiresAt',
|
||||
token: 'token',
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt',
|
||||
ipAddress: 'ipAddress',
|
||||
userAgent: 'userAgent',
|
||||
userId: 'userId',
|
||||
impersonatedBy: 'impersonatedBy'
|
||||
};
|
||||
|
||||
exports.Prisma.UserScalarFieldEnum = {
|
||||
id: 'id',
|
||||
name: 'name',
|
||||
email: 'email',
|
||||
emailVerified: 'emailVerified',
|
||||
image: 'image',
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt',
|
||||
username: 'username',
|
||||
displayUsername: 'displayUsername',
|
||||
role: 'role',
|
||||
banned: 'banned',
|
||||
banReason: 'banReason',
|
||||
banExpires: 'banExpires'
|
||||
};
|
||||
|
||||
exports.Prisma.VerificationScalarFieldEnum = {
|
||||
id: 'id',
|
||||
identifier: 'identifier',
|
||||
value: 'value',
|
||||
expiresAt: 'expiresAt',
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt'
|
||||
};
|
||||
|
||||
exports.Prisma.SortOrder = {
|
||||
asc: 'asc',
|
||||
desc: 'desc'
|
||||
};
|
||||
|
||||
exports.Prisma.QueryMode = {
|
||||
default: 'default',
|
||||
insensitive: 'insensitive'
|
||||
};
|
||||
|
||||
exports.Prisma.NullsOrder = {
|
||||
first: 'first',
|
||||
last: 'last'
|
||||
};
|
||||
|
||||
|
||||
exports.Prisma.ModelName = {
|
||||
account: 'account',
|
||||
passkey: 'passkey',
|
||||
session: 'session',
|
||||
user: 'user',
|
||||
verification: 'verification'
|
||||
};
|
||||
|
||||
/**
|
||||
* This is a stub Prisma Client that will error at runtime if called.
|
||||
*/
|
||||
class PrismaClient {
|
||||
constructor() {
|
||||
return new Proxy(this, {
|
||||
get(target, prop) {
|
||||
let message
|
||||
const runtime = getRuntime()
|
||||
if (runtime.isEdge) {
|
||||
message = `PrismaClient is not configured to run in ${runtime.prettyName}. In order to run Prisma Client on edge runtime, either:
|
||||
- Use Prisma Accelerate: https://pris.ly/d/accelerate
|
||||
- Use Driver Adapters: https://pris.ly/d/driver-adapters
|
||||
`;
|
||||
} else {
|
||||
message = 'PrismaClient is unable to run in this browser environment, or has been bundled for the browser (running in `' + runtime.prettyName + '`).'
|
||||
}
|
||||
|
||||
message += `
|
||||
If this is unexpected, please open an issue: https://pris.ly/prisma-prisma-bug-report`
|
||||
|
||||
throw new Error(message)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
exports.PrismaClient = PrismaClient
|
||||
|
||||
Object.assign(exports, Prisma)
|
26
package.json
|
@ -17,19 +17,30 @@
|
|||
"@astrojs/react": "^4.1.6",
|
||||
"@astrojs/rss": "^4.0.11",
|
||||
"@astrojs/tailwind": "^5.1.5",
|
||||
"@electric-sql/pglite": "^0.3.0",
|
||||
"@fontsource/libre-baskerville": "^5.1.1",
|
||||
"@fontsource/nunito": "^5.1.1",
|
||||
"@prisma/client": "6.7.0",
|
||||
"@types/lodash": "^4.17.14",
|
||||
"@types/react": "^18.3.18",
|
||||
"@types/react-dom": "^18.3.5",
|
||||
"astro": "^5.1.8",
|
||||
"astro-auto-import": "^0.4.4",
|
||||
"astro-icon": "^1.1.5",
|
||||
"astro-m2dx": "^0.7.16",
|
||||
"astro-mdx-code-blocks": "^0.0.6",
|
||||
"better-auth": "^1.2.7",
|
||||
"date-fns": "^4.1.0",
|
||||
"date-fns-tz": "^3.2.0",
|
||||
"dotenv": "^16.5.0",
|
||||
"drizzle-orm": "^0.43.1",
|
||||
"feed": "^4.2.2",
|
||||
"github-slugger": "^2.0.0",
|
||||
"linkedom": "^0.18.6",
|
||||
"lodash": "^4.17.21",
|
||||
"node-html-parser": "^6.1.13",
|
||||
"pg": "^8.15.6",
|
||||
"postgres": "^3.4.5",
|
||||
"prism": "^4.1.2",
|
||||
"prism-react-renderer": "^2.4.1",
|
||||
"prismjs": "^1.29.0",
|
||||
|
@ -37,20 +48,21 @@
|
|||
"react-code-block": "^1.1.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-icons": "^5.4.0",
|
||||
"react-qr-code": "^2.0.15",
|
||||
"rehype-mdx-code-props": "^3.0.1",
|
||||
"rehype-pretty-code": "^0.14.0",
|
||||
"rehype-slug": "^6.0.0",
|
||||
"sharp": "^0.33.5",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"typescript": "^5.7.3",
|
||||
"@types/lodash": "^4.17.14",
|
||||
"astro-auto-import": "^0.4.4",
|
||||
"astro-mdx-code-blocks": "^0.0.6",
|
||||
"lodash": "^4.17.21"
|
||||
"tailwindcss-animate": "^1.0.7"
|
||||
},
|
||||
"packageManager": "pnpm@9.10.0+sha512.73a29afa36a0d092ece5271de5177ecbf8318d454ecd701343131b8ebc0c1a91c487da46ab77c8e596d6acf1461e3594ced4becedf8921b074fbd8653ed7051c",
|
||||
"devDependencies": {
|
||||
|
||||
"@types/node": "^22.15.15",
|
||||
"@types/pg": "^8.15.0",
|
||||
"drizzle-kit": "^0.31.1",
|
||||
"prisma": "^6.7.0",
|
||||
"tsx": "^4.19.4",
|
||||
"typescript": "^5.7.3"
|
||||
}
|
||||
}
|
||||
|
|
1284
pnpm-lock.yaml
generated
133
prisma/schema.prisma
Normal file
|
@ -0,0 +1,133 @@
|
|||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
output = "../node_modules/.prisma/client"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
directUrl = env("DIRECT_URL")
|
||||
}
|
||||
|
||||
// COMMENTS STUFF
|
||||
|
||||
model Comment {
|
||||
id Int @id @default(autoincrement())
|
||||
authorId String
|
||||
author User @relation("Author", fields: [authorId], references: [id], onDelete: Cascade)
|
||||
date DateTime
|
||||
dateUpdated DateTime?
|
||||
|
||||
contents String
|
||||
|
||||
like_count Int
|
||||
likes Likes[] @relation("Likes")
|
||||
|
||||
pinned Boolean
|
||||
|
||||
parentId Int
|
||||
parent Comment @relation("Replies", fields: [parentId], references: [id], onDelete: Cascade)
|
||||
replies Comment[] @relation("Replies")
|
||||
|
||||
@@map("comment")
|
||||
}
|
||||
|
||||
model Likes {
|
||||
commentId Int
|
||||
comment Comment @relation("Likes", fields: [commentId], references: [id], onDelete: Cascade)
|
||||
userId String
|
||||
user User @relation("Likes", fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@id([commentId, userId])
|
||||
@@map("likes")
|
||||
}
|
||||
|
||||
// AUTH STUFF
|
||||
|
||||
model User {
|
||||
id String @id
|
||||
name String
|
||||
email String
|
||||
emailVerified Boolean
|
||||
image String?
|
||||
createdAt DateTime
|
||||
updatedAt DateTime
|
||||
username String?
|
||||
displayUsername String?
|
||||
role String?
|
||||
banned Boolean?
|
||||
banReason String?
|
||||
banExpires DateTime?
|
||||
sessions Session[]
|
||||
accounts Account[]
|
||||
passkeys Passkey[]
|
||||
|
||||
comments Comment[] @relation("Author")
|
||||
likes Likes[] @relation("Likes")
|
||||
|
||||
@@unique([email])
|
||||
@@unique([username])
|
||||
@@map("user")
|
||||
}
|
||||
|
||||
model Session {
|
||||
id String @id
|
||||
expiresAt DateTime
|
||||
token String
|
||||
createdAt DateTime
|
||||
updatedAt DateTime
|
||||
ipAddress String?
|
||||
userAgent String?
|
||||
userId String
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
impersonatedBy String?
|
||||
|
||||
@@unique([token])
|
||||
@@map("session")
|
||||
}
|
||||
|
||||
model Account {
|
||||
id String @id
|
||||
accountId String
|
||||
providerId String
|
||||
userId String
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
accessToken String?
|
||||
refreshToken String?
|
||||
idToken String?
|
||||
accessTokenExpiresAt DateTime?
|
||||
refreshTokenExpiresAt DateTime?
|
||||
scope String?
|
||||
password String?
|
||||
createdAt DateTime
|
||||
updatedAt DateTime
|
||||
|
||||
@@map("account")
|
||||
}
|
||||
|
||||
model Verification {
|
||||
id String @id
|
||||
identifier String
|
||||
value String
|
||||
expiresAt DateTime
|
||||
createdAt DateTime?
|
||||
updatedAt DateTime?
|
||||
|
||||
@@map("verification")
|
||||
}
|
||||
|
||||
model Passkey {
|
||||
id String @id
|
||||
name String?
|
||||
publicKey String
|
||||
userId String
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
credentialID String
|
||||
counter Int
|
||||
deviceType String
|
||||
backedUp Boolean
|
||||
transports String?
|
||||
createdAt DateTime?
|
||||
|
||||
@@map("passkey")
|
||||
}
|
BIN
public/eleboog_button.jpg
Normal file
After Width: | Height: | Size: 3.4 KiB |
BIN
public/eleboog_button_light.jpg
Normal file
After Width: | Height: | Size: 3.2 KiB |
31
src/actions/index.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
import { defineAction, ActionError } from 'astro:actions';
|
||||
import { z } from 'astro:schema';
|
||||
|
||||
import { authClient } from "../lib/auth-client"; //import the auth client
|
||||
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
export const server = {
|
||||
updateUsername: defineAction({
|
||||
input: z.object({
|
||||
id: z.string().uuid(),
|
||||
username: z.string()
|
||||
.min(3, "Username too short")
|
||||
.max(30, "Username too long")
|
||||
.regex(/^[a-zA-Z0-9\_\.]+$/, "Illegal characters in username"),
|
||||
}),
|
||||
handler: async (input) => {
|
||||
prisma.$connect
|
||||
const user = await prisma.user.update({
|
||||
where: {
|
||||
id: input.id,
|
||||
},
|
||||
data: {
|
||||
username: input.username,
|
||||
}
|
||||
});
|
||||
return user;
|
||||
}
|
||||
})
|
||||
}
|
106
src/components/Comments.astro
Normal file
|
@ -0,0 +1,106 @@
|
|||
---
|
||||
import HR from "../components/HR.astro";
|
||||
---
|
||||
<style>
|
||||
@import url('/src/styles/globals.css');
|
||||
|
||||
@tailwind components;
|
||||
|
||||
@layer components {
|
||||
h2 {
|
||||
@apply text-2xl font-serif text-title font-bold;
|
||||
}
|
||||
a {
|
||||
@apply font-serif text-subtitle hover:underline;
|
||||
}
|
||||
.commentheader > a {
|
||||
@apply font-mono text-lg text-subtitle hover:underline;
|
||||
}
|
||||
.commentfooter > a {
|
||||
@apply font-mono hover:underline;
|
||||
}
|
||||
blockquote {
|
||||
@apply p-2 pl-4 mb-2 border-l-4 border-l-gray-600 dark:border-l-gray-300 rounded-l-md bg-crusta-100 dark:bg-night-950 dark:bg-opacity-80 text-current;
|
||||
}
|
||||
textarea {
|
||||
@apply p-2 w-full bg-crusta-100 dark:bg-night-950 dark:bg-opacity-80;
|
||||
}
|
||||
.textboxfooter {
|
||||
@apply font-mono;
|
||||
}
|
||||
.textboxfooter > a {
|
||||
@apply font-mono;
|
||||
}
|
||||
input {
|
||||
@apply font-mono cursor-pointer text-subtitle hover:underline;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<div class="comments">
|
||||
<HR/>
|
||||
<h2 class="">Comments</h2>
|
||||
<!-- <p>To leave your own comment, please <a href="/">login or register</a> your eleboog.com account.</p> -->
|
||||
<form method="POST">
|
||||
<label for="commentbox">Leave a comment as <a class="font-mono text-lg">kebokyo</a> — Markdown enabled ( <a class="font-mono text-lg">huh?</a> )</label>
|
||||
<textarea id="commentbox" name="commentbox" rows="4"/>
|
||||
|
||||
<div class="textboxfooter">// <input type="submit" value="submit!"/> or press ctrl (cmd) + enter</div>
|
||||
</form>
|
||||
<p>By leaving a comment, you agree to the eleboog.com <a>code of conduct</a>. Dishonorable behavior may lead to account limits or termination.</p>
|
||||
<div class="my-2 border-b-2 border-crusta-500 dark:border-night-400 border-opacity-50 w-full"/>
|
||||
<div class="commentheader mb-0">★ <a class="font-mono text-lg">kebokyo</a> (it/its) — May 4, 2025</div>
|
||||
<div class="commentblock pl-2 pt-1 mb-1 border-l-4 border-crusta-500 dark:border-night-400">
|
||||
This is a markup of what I want the comments to look like.
|
||||
<br/><br/>Like the rest of the site, minimal visual elements & unique text formatting is used to give a text-focused approach that
|
||||
can easily be scaled back for "lite" or gemini versions of the site.
|
||||
<div class="commentsig font-mono mb-0">
|
||||
★ site owner of eleboog.com
|
||||
</div>
|
||||
<div class="commentfooter font-mono mb-2">
|
||||
2 <span class="font-sans text-sm">♥</span> // <a>reply</a> - <a>quote</a> // <a>edit</a> - <a class="text-red-500 dark:text-red-400">delete x</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="commentindent pl-2 mb-2 border-l-2 border-b-2 border-gray-600 dark:border-gray-300">
|
||||
<div class="commentheader mb-0">• <a style="">user</a> (they/them) — May 4, 2025</div>
|
||||
<div class="commentblock pl-2 pt-1 mb-1 border-l-4 border-crusta-500 dark:border-night-400">
|
||||
This would be a reply to the above comment.
|
||||
<div class="commentsig font-mono mb-0">
|
||||
· insert signature here
|
||||
</div>
|
||||
<div class="commentfooter font-mono mb-2">
|
||||
3 <span class="font-sans text-sm">♥</span>✓ (<a>unlike</a>) // <a>reply</a> - <a>quote</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="commentindent pl-2 mb-2 border-l-2 border-b-2 border-gray-600 dark:border-gray-300">
|
||||
<div class="commentheader mb-0">• <a style="">user2</a> (they/them) — May 4, 2025</div>
|
||||
<div class="commentblock pl-2 pt-1 mb-1 border-l-4 border-crusta-500 dark:border-night-400">
|
||||
<blockquote>
|
||||
<p><i><a class="font-mono">user</a> said on May 4, 2025...</i> (<a class="font-mono">jump to ↑</a>)</p>
|
||||
This would be a reply to the above comment.
|
||||
</blockquote>
|
||||
This would be an example of a "quoted" reply.
|
||||
<div class="commentsig font-mono mb-0">
|
||||
· babobee babodah
|
||||
</div>
|
||||
<div class="commentfooter font-mono mb-2">
|
||||
2 <span class="font-sans text-sm">♥</span> (<a>like?</a>) // <a>reply</a> - <a>quote</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="commentindent pl-2 mb-2 border-l-2 border-b-2 border-gray-600 dark:border-gray-300">
|
||||
<i>3 more replies in thread...</i> <a class="font-mono text-lg">explore →</a>
|
||||
</div>
|
||||
<div class="commentheader mb-0">• <a style="">user3</a> (they/them) — May 4, 2025</div>
|
||||
<div class="commentblock pl-2 pt-1 mb-2 border-l-4 border-b-2 border-crusta-500 dark:border-night-400">
|
||||
One more reply for good measure...
|
||||
<div class="commentsig font-mono mb-0">
|
||||
· fdhsfklasdfijsdlfnkasfnsaf
|
||||
</div>
|
||||
<div class="commentfooter font-mono mb-2">
|
||||
2 <span class="font-sans text-sm">♥</span> (<a>like?</a>) // <a>reply</a> - <a>quote</a>
|
||||
</div>
|
||||
</div>
|
||||
<i>3 more replies in thread...</i> <a class="font-mono text-lg">explore →</a>
|
||||
</div>
|
||||
<i>7 more replies in thread...</i> <a class="font-mono text-lg">explore →</a>
|
||||
</div>
|
||||
</div>
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
const {current} = Astro.props;
|
||||
|
||||
const user = Astro.locals.user;
|
||||
|
||||
---
|
||||
|
||||
<div>
|
||||
|
@ -37,5 +39,14 @@ const {current} = Astro.props;
|
|||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- auth shenanigans -->
|
||||
<div class="authbar mb-4 font-mono">
|
||||
{Astro.locals.session ? <div>
|
||||
{user?.name} — <a href="/account/profile" class="text-subtitle hover:underline">profile</a>
|
||||
/ <a href="/account/settings" class="text-subtitle hover:underline">settings</a>
|
||||
/ <button id="logout" class="text-subtitle hover:underline">logout</button>
|
||||
</div> : <div>
|
||||
guest — <a href="/account/login" class="font-serif text-sm text-subtitle hover:underline">login or register</a>
|
||||
</div>}
|
||||
</div>
|
||||
</div>
|
|
@ -12,7 +12,7 @@ export interface Props {
|
|||
function getSVG(name: string) {
|
||||
const filepath = `/src/svg/${name}.svg`;
|
||||
const files = import.meta.glob<string>('/src/svg/**/*.svg', {
|
||||
as: 'raw', eager: true,
|
||||
query: '?raw', import: 'default', eager: true,
|
||||
});
|
||||
|
||||
if (!(filepath in files)) {
|
||||
|
|
113
src/components/mdx/MDXTableOfContents.astro
Normal file
|
@ -0,0 +1,113 @@
|
|||
---
|
||||
|
||||
// I stole ALLLLLL of this from https://webreaper.dev/posts/astro-accessible-accordion/
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
}
|
||||
|
||||
const { title } = Astro.props as Props;
|
||||
|
||||
import HR from "../HR.astro";
|
||||
|
||||
---
|
||||
|
||||
<noscript>
|
||||
<h3 class="text-lg text-title font-mono">table of contents</h3>
|
||||
<slot/>
|
||||
</noscript>
|
||||
|
||||
<div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="accordion group relative mb-2 rounded-md border border-crusta-900 dark:border-indigo-400 hidden">
|
||||
<button
|
||||
class="accordion__button flex w-full flex-1 items-center justify-between gap-2 p-3 text-left font-medium transition hover:text-subtitle sm:px-4"
|
||||
type="button"
|
||||
id={`${title} accordion menu button`}
|
||||
aria-expanded="false"
|
||||
aria-controls={`${title} accordion menu content`}
|
||||
>
|
||||
<span class="text-xl font-serif">{title}</span>
|
||||
|
||||
<!-- if using astro and the astro-icon package
|
||||
<Icon
|
||||
name="tabler:chevron-down"
|
||||
aria-hidden="true"
|
||||
class="accordion__chevron h-7 w-7 shrink-0 transition-transform"
|
||||
/>
|
||||
-->
|
||||
|
||||
<!-- use this is not using astro-icon (or another SVG you like) -->
|
||||
<svg
|
||||
class="accordion__chevron h-7 w-7 shrink-0 transition-transform"
|
||||
aria-hidden="true"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
><path
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="m6 9l6 6l6-6"></path></svg
|
||||
>
|
||||
|
||||
</button>
|
||||
|
||||
<div
|
||||
id={`${title} accordion menu content`}
|
||||
aria-labelledby={`${title} accordion menu button`}
|
||||
class="accordion__content hidden max-h-0 overflow-hidden px-3 transition-all duration-300 ease-in-out sm:px-4"
|
||||
>
|
||||
<HR class="mt-0"/>
|
||||
<div class="prose mb-4 items-center mt-1 max-w-full transition-[height]">
|
||||
<slot/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function accordionSetup() {
|
||||
const menus = document.querySelectorAll(".accordion") as NodeListOf<HTMLElement>; // set this up on each menu
|
||||
menus.forEach((menu) => {
|
||||
menu.classList.remove("hidden");
|
||||
|
||||
const button = menu.querySelector(".accordion__button") as HTMLElement; // the clickable banner
|
||||
const chevron = menu.querySelector(".accordion__chevron") as HTMLElement; // the chevron icon that animates
|
||||
const content = menu.querySelector(".accordion__content") as HTMLElement; // the stuff that's revealed when open
|
||||
|
||||
if (button && content && chevron) {
|
||||
button.addEventListener("click", (event) => {
|
||||
if (!menu.classList.contains("active")) { // if closed, stop having it be closed!
|
||||
menu.classList.add("active");
|
||||
button.setAttribute("aria-expanded", "true");
|
||||
|
||||
// we need to set the max height to the height of the accordion object so the animations work correctly
|
||||
content.classList.remove("hidden");
|
||||
content.style.maxHeight = content.scrollHeight + "px";
|
||||
chevron.classList.add("rotate-180");
|
||||
} else { // if open, stop having it be open!!!!
|
||||
menu.classList.remove("active");
|
||||
button.setAttribute("aria-expanded", "false");
|
||||
|
||||
content.style.maxHeight = '0px';
|
||||
chevron.classList.remove("rotate-180");
|
||||
|
||||
// make text invisible after animation
|
||||
setTimeout(() => { content.classList.add("hidden") }, 300);
|
||||
}
|
||||
event.preventDefault();
|
||||
return false;
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
accordionSetup();
|
||||
|
||||
document.addEventListener("astro:after-swap", accordionSetup);
|
||||
</script>
|
47
src/content/posts/2025-03-11-jwl-03.mdx
Normal file
|
@ -0,0 +1,47 @@
|
|||
---
|
||||
date: 2025-03-11 13:00:00
|
||||
title: A Quick Dive into Intel x86-64 Bytecode (JWL 02)
|
||||
summary: Intel x86-64 assembly can be daunting just by itself... but what if you had to write *in* bytecode? Here's a brief tutorial to get you started. The second entry into my JustWriteLol series.
|
||||
---
|
||||
|
||||
If you've ever delved into writing assembly before, you may know that it takes a *vastly* different approach than writing in most other languages,
|
||||
even languages fairly close to assembly like C. Registers, memory addresses, opcodes, all of it can be pretty daunting just on their own...
|
||||
|
||||
But what if you had to do more than just write the instructions in text form? What if... you had to write. *every. single. byte.* of **every. single.
|
||||
line. of assembly.**
|
||||
|
||||
You might think that this is the craziest thing you have ever heard. Who in their right mind would do such a thing? And who on the face of Planet
|
||||
Earth would actually find this... fun????
|
||||
|
||||
Me. I'm the problem. It's me.
|
||||
|
||||
I've always found assembly a fun change of pace from most other programming since I took a class all about it required for my major. I already
|
||||
knew about how assembly can be represented in bytecode, but I never really had to directly write it myself... until this course I'm taking now,
|
||||
where the professor decided to run x86-64 assembly code in C++ like this:
|
||||
|
||||
```cpp
|
||||
char *prog;
|
||||
int value;
|
||||
int p_offset = 0;
|
||||
|
||||
prog = (char*) mmap(0, 50000, PROT_EXEC | PROT_READ | PROT_WRITE,
|
||||
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
|
||||
prog[p_offset++] = 0xb8;
|
||||
prog[p_offset++] = 0x2a;
|
||||
prog[p_offset++] = 0x00;
|
||||
prog[p_offset++] = 0x00;
|
||||
prog[p_offset++] = 0x00;
|
||||
prog[p_offset++] = 0xc3;
|
||||
|
||||
value = (int(*)(void) prog)();
|
||||
|
||||
cout << value << endl;
|
||||
```
|
||||
```bash
|
||||
$ ./a.out
|
||||
42
|
||||
```
|
||||
<MDXImage src="https://media1.tenor.com/m/giGudNYLk_sAAAAd/benny-fallout.gif" alt='Benny from Fallout: New Vegas turning around to face you and exclaiming, in shock at your not-dead-ed-ness: "What in the goddamn...?"'/>
|
||||
|
||||
Yes, that's really how he wrote it. Yes, *it actually works*. Maybe Intel engineers discovered the meaning to life after all.
|
159
src/content/posts/2025-03-19-jwl-gunsmith.mdx
Normal file
|
@ -0,0 +1,159 @@
|
|||
---
|
||||
date: 2025-03-19 13:00:00
|
||||
title: A Tale of Three Zoomies (JWL 02)
|
||||
summary: Finally, another COD article. I compare the guns with the fastest movement possible in MWII, MWIII, and BO6 to try to figure out why BO6 feels so... boring.
|
||||
---
|
||||
|
||||
quick outline
|
||||
|
||||
# intro
|
||||
- introduce [TheXclusiveAce video](https://www.youtube.com/watch?v=bUFG9xNj-2g)
|
||||
and "you could get comparable results with any SMG" comment
|
||||
- explain why i want to compare this build with similar builds in MWII & III
|
||||
- MWIII is easy: previous game, we all know previous game is always better ;3
|
||||
- for MWII, I think a lot of what makes MWIII great is because of the
|
||||
**foundation** built by MWII, so I want to bring that game into the
|
||||
conversation too
|
||||
- thesis statement: **the reason why BO6's weapons feel so boring compared to
|
||||
previous games is entirely because of how Gunsmith works.**
|
||||
- Gunsmith is why the weapons in MWIII and even MWII all feel unique
|
||||
from each other, no matter if the category is under- or oversaturated
|
||||
- Gunsmith is why the weapons in BO6 all feel the same and generally
|
||||
boring to play with, especially if a category is oversaturated (lots of
|
||||
already similar-feeling weapons) or *under*saturated (weapons don't
|
||||
feel unique enough to get past the lack of choices in that category)
|
||||
|
||||
# methodology
|
||||
- "m/s" is kinda fucking arbitrary
|
||||
- we aren't gappy from jojolion and can measure the exact distances of
|
||||
objects from us with our minds, the closest we can get is the ping system
|
||||
and even then it isn't the most accurate
|
||||
- if we were working in Source where we both a. had our speed measured in
|
||||
a unit we can directly measure in-game (Hammer units) and b. already had
|
||||
plentiful resources for how in-game elements are sized relative to that
|
||||
unit (e. our camera is 64 Hu from the ground, so if you can't look over an
|
||||
object, it's guaranteed to be 64 Hu or higher)... this would all be so
|
||||
much easier... but we're on a different branch of the quake engine lmao
|
||||
- so, for easier comparisons, we're going to establish some baseline movement
|
||||
speeds based on certain weapons from each game
|
||||
- we're looking at an AR for an overall baseline and an SMG for a
|
||||
baseline specific to SMG's (since all 3 of our YYassiffied weapons are
|
||||
SMG's)
|
||||
- both weapons will have a default value and an "optimized" value with
|
||||
movement speed attachments so we can compare the weapons both with and
|
||||
without attachments (and also compare how much attachments *improve*
|
||||
the weapons from base)
|
||||
- BO6: XM4 for overall, C9 for SMG
|
||||
- MWII: M4 for overall, MP5 for SMG
|
||||
- MWIII: MTZ-556 for overall, Rival 9 for SMG
|
||||
- we could use the MWII weapons... but that feels like a. a copout and
|
||||
b. an unfair comparison (since those weapons were nerfed into
|
||||
the ground for 90% of MWIII's lifecycle and even now have different
|
||||
stats than their original MWII versions to account for 150 HP and etc.)
|
||||
- MTZ-556 (CZ Bren 2) feels like the closest analogue to the M4, and
|
||||
the Rival 9 (CZ Scorpion EVO 3) ditto MP5
|
||||
- finally, quick disclaimer: **I do not own Black Ops 6.**
|
||||
- I *do* own MWII and MWIII tho
|
||||
- stats for MWIII will be taken from in-game stats when possible
|
||||
- stats for MWII will be taken from XclusiveAce Gun Guides and balance
|
||||
patch videos (to reflect their stats as of today)
|
||||
- stats for BO6 will be taken from TrueGameData as well as XclusiveAce
|
||||
videos
|
||||
- for attachments, figuring out how much they help or hinder stats depends
|
||||
on the game again
|
||||
- MWIII is e z p z, in-game stats babyyyyy
|
||||
- BO6 is also e z p z due to the nature of how Gunsmith was designed:
|
||||
videos exist covering *every single attachment in a category* that
|
||||
can be used as a good reference (though it should be noted that
|
||||
the raw stats don't *always* stay the same for every gun, like
|
||||
with rapid fire's damage range reduction sometimes not applying
|
||||
to certain guns with seemingly no rhyme or reason)
|
||||
- MWII will be trickier, will refer to gun guides and other content
|
||||
to figure out what the attachments do
|
||||
|
||||
# The Weapons of Choice
|
||||
(if video, make a YTP joke about the Fatboy Slim song)
|
||||
(also )
|
||||
## MWII: Fennec 45
|
||||
- real life gun: Kriss Vector .45 ACP
|
||||
- famous for the Super V recoil-mitigation system that has *only* been
|
||||
accurately modeled in one video game: Insurgency
|
||||
- the most notable thing about the fennec is that it has the highest sprint
|
||||
speed in MWII, *even more than the combat knife*
|
||||
- todo: go into MWII and test if the sprint speed is still ridonkulous
|
||||
like this, including w/ the new melee's (iirc there's a melee that's
|
||||
faster than the combat knife but idk for sure)
|
||||
- we can further improve our movement speeds with some unique attachments
|
||||
- the double tap mod actually boosts our movement speeds since it's a
|
||||
smaller magazine than the base
|
||||
- you can also slap on the ftac 8.5 recon barrel (not to be confused
|
||||
with the ftac recon pistol based off the TEC-9 wow Infinity Ward you
|
||||
are so good at naming things keep it up) and the ftac stock cap to
|
||||
further boost movement speeds
|
||||
- the other two slots are freebies: it's probably a good idea to
|
||||
add a rear grip for improved sprint-to-fire times and other
|
||||
similar stats along with the vlk 7mw lazer so you can get better
|
||||
hipfire spread and not have to worry as much about ADS and recoil
|
||||
and etc
|
||||
|
||||
## MWIII: Superi 46
|
||||
- real life gun: CMMG Four-Six (Banshee)
|
||||
- this isn't even a military firearm, it's a civilian gun l m f a o
|
||||
- the Superi has the fastest aim walking speed @ 3.6 m/s, faster than any
|
||||
other SMG in the game
|
||||
- but the real reason that the Superi shines is because of its **attachments**
|
||||
that allow you to elevate the gun far beyond even the melee weapons of MWIII
|
||||
- bore 99 short barrel: walking +7% sprint +8% tac sprint +7%
|
||||
- rescue 9 stock: walking +11% sprint +8% tac sprint +7%
|
||||
- jak slash: walking +4% crouch +5% sprint +4% tac sprint +3%
|
||||
- 20 round mag: ()
|
||||
- we then have a freebie slot we can use for anything we want. a good
|
||||
default is the quartermaster suppressor to counteract the recoil
|
||||
penalties from all of our zoomy attachments, but most of the time
|
||||
you're going to be running overkill with this and keeping it in your
|
||||
pocket for zooming across the map, so this slot doesn't really matter
|
||||
- the resulting movement speeds you get are 6.2 for base walking speed,
|
||||
7.0 for regular sprint, and **8.6** for tactical sprint, which is
|
||||
leagues faster than the Karambit, the fastest melee weapon in the game
|
||||
|
||||
## BO6: Saug
|
||||
- real life gun: ....uhhhhhh
|
||||
- in actuality, this gun is based off the Saug from BO4, a near-future
|
||||
game where they didn't have to give a shit about modeling after real guns...
|
||||
so things got a bit wacky with this one
|
||||
- the saug has above average movement speeds across the board due to how
|
||||
smol it is, which means it's a prime choice for further modifications
|
||||
- however, the attachment selection is far more limited
|
||||
- the best attachment for improving your sprint speed is actually
|
||||
a **foregrip**: the Ranger Foregrip improves your sprint speed by 5% and
|
||||
your tactical sprint speed by 4% (at least on SMG's)
|
||||
- the *only* other attachment in the entire game that impacts your
|
||||
movement speeds is the stock attachment. the stock that you put here
|
||||
depends on whether you want sprint speed over aim walking speed.
|
||||
- if max speed is what you need, the No Stock attachment improves
|
||||
tactical sprint speeds **but not regular sprint speeds** by
|
||||
around 25% and base walking speeds by 15% forwards and 20% strafing.
|
||||
- if you want better aim walking speed, the Infiltrator Stock
|
||||
improves your aim walking speed by 17.5% forwards and 20% strafing.
|
||||
- only the no stock attachment can improve your tactical sprint speed
|
||||
here; no other stock allows you to improve any sprint speeds.
|
||||
- since those are the only two attachments that boost movement speed
|
||||
in this game, *the other three slots are all freebies*.
|
||||
- typically it's good to put on the Ergonomic Grip or the CQB Grip.
|
||||
they both improve slide to fire and dive to fire times, but it's
|
||||
usually better to to use the Ergonomic Grip for reasons covered
|
||||
in [this video](https://www.youtube.com/watch?v=IFgJpllvEvI)
|
||||
- since extended mag I *doesn't* harm our movement speeds in
|
||||
BO6 (as opposed to MWII & MWIII where most extended mags will
|
||||
impact movement speed in some way), it's another freebie with only
|
||||
upsides for us
|
||||
- the last attachment is 120% a freebie, we can add a muzzle, an
|
||||
optic, barrel, rapid fire... but for this build since we're going
|
||||
to be running around a lot it may be good to keep ourselves
|
||||
off the radar with a suppressor
|
||||
- note that **none** of these attachments are unique to the Saug specifically:
|
||||
if you wanted to use this exact same build on literally any other gun in the
|
||||
game, you totally could (with some specific values being slightly different ofc)
|
||||
- the Saug is only chosen because it has the best *base* values out of any
|
||||
other SMG: as Ace stated, you can choose *any other SMG* and get slightly
|
||||
worse but still satisfactory results
|
19
src/content/posts/2025-04-01-i-am-the-fool.mdx
Normal file
|
@ -0,0 +1,19 @@
|
|||
---
|
||||
title: Welp, I guess I'm the April Fool (JWL 02)
|
||||
summary: Did I really think I was going to be able to do this? ...Maybe I still can.
|
||||
date: 2025-04-01 13:00:00
|
||||
---
|
||||
Tuesday, April 4, 2025\
|
||||
4:11 PM
|
||||
|
||||
I went to Starbucks today to attempt to get some work done. I skipped class because I woke up at 10 when my classes started at 11 and I no longer
|
||||
have access to a car... so instead of doing anything productive I just stayed in bed for three hours watching YouTube and playing Balatro.
|
||||
I knew I needed to break this rut, so I got out of the house and hopped on the bus to huff my things to Starbucks.
|
||||
|
||||
I ordered a Iced Cherry Chai because it seemed cute, and I asked for it to be put in one of those fancy glasses. They accidentally made it in a regular cup first,
|
||||
so when my order was put out they asked me if I wanted both... I'm not gonna say no to free Starbucks lol. It also meant I got to see that
|
||||
at least one of the employees recognized me and wrote a cute note on my to-go cup, which absolutely warmed my heart.
|
||||
|
||||
|
||||
|
||||
Finally, to really seal home the "journal entry" feel, [here's a deep cut from the oily depths of the YouTube algorithm.](https://www.youtube.com/watch?v=-psx38I_6vY)
|
20
src/content/posts/2025-04-24-small-socials.mdx
Normal file
|
@ -0,0 +1,20 @@
|
|||
---
|
||||
title: We need small social medias (JWL 02)
|
||||
summary: Social media shouldn't be the place where you can find everyone. It should be a place where you find a group of people you care about. A very belated entry into my JustWriteLol series.
|
||||
date: 2025-04-24 13:00:00
|
||||
---
|
||||
|
||||
I was going down an internet rabbithole when I found an interesting statement on a product's website. The product is [Letterbird](https://letterbird.co/?utm_source=lbform&utm_medium=lbformfooter),
|
||||
a SaaS that lets you put a contact form for your website without having to design it yourself. The bottom of the page has a FAQ that ends with
|
||||
the question, "What if I have another question or idea for a new feature?" The answer lists a group of places they can be contacted: Bluesky,
|
||||
Mastodon, Threads, and X... but it also leaves this remark after those links:
|
||||
|
||||
> (Gosh, can we all just agree on one social media network already?)
|
||||
|
||||
...No? I don't really want to?
|
||||
|
||||
I have multiple different social medias. I have a Mastodon account, a Bluesky account, Telegram and Discord accounts
|
||||
(which at this point, pretty much *are* social medias)... and they all exist for different purposes. They all exists to capture different groups
|
||||
of people. And I think that is a **good** thing.
|
||||
|
||||
In fact,
|
8
src/content/posts/2030-01-01-comment-mockup.mdx
Normal file
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
date: 2030-01-01 13:00:00
|
||||
title: Comment Markup
|
||||
summary: haha penis
|
||||
---
|
||||
import Comments from "../../components/Comments.astro"
|
||||
|
||||
Testity test test
|
10
src/env.d.ts
vendored
|
@ -1 +1,9 @@
|
|||
/// <reference path="../.astro/types.d.ts" />
|
||||
/// <reference path="../.astro/types.d.ts" />
|
||||
|
||||
declare namespace App {
|
||||
// Note: 'import {} from ""' syntax does not work in .d.ts files.
|
||||
interface Locals {
|
||||
user: import("better-auth").User | null;
|
||||
session: import("better-auth").Session | null;
|
||||
}
|
||||
}
|
|
@ -13,6 +13,7 @@ import Footer from '../components/Footer.astro'
|
|||
import MDXImage from '../components/mdx/MDXImage.astro'
|
||||
|
||||
import HR from '../components/HR.astro'
|
||||
import Comments from '../components/Comments.astro'
|
||||
|
||||
const fm = Astro.props.frontmatter
|
||||
const headings = await Astro.props.headings
|
||||
|
@ -89,6 +90,7 @@ const numberToWord = (num: number) => {
|
|||
<slot/>
|
||||
</article>
|
||||
</main>
|
||||
<Comments/>
|
||||
<Footer/>
|
||||
</body>
|
||||
</html>
|
11
src/lib/auth-client.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
import { createAuthClient } from "better-auth/react"
|
||||
import { usernameClient, adminClient, passkeyClient } from "better-auth/client/plugins"
|
||||
export const authClient = createAuthClient({
|
||||
/** The base URL of the server (optional if you're using the same domain) */
|
||||
baseURL: "http://localhost:4321",
|
||||
plugins: [
|
||||
usernameClient(), adminClient(), passkeyClient()
|
||||
]
|
||||
})
|
||||
|
||||
export const { signIn, signOut, useSession } = authClient;
|
30
src/lib/auth.ts
Normal file
|
@ -0,0 +1,30 @@
|
|||
import 'dotenv/config'
|
||||
import { betterAuth } from "better-auth";
|
||||
import { username, admin } from "better-auth/plugins"
|
||||
import { passkey } from "better-auth/plugins/passkey";
|
||||
//import { Pool } from "pg";
|
||||
import { prismaAdapter } from "better-auth/adapters/prisma";
|
||||
import prisma from "./prisma"
|
||||
|
||||
export const auth = betterAuth({
|
||||
plugins: [
|
||||
username(), admin(),
|
||||
passkey({
|
||||
rpID: "localhost",
|
||||
rpName: "eleboog.com"
|
||||
})
|
||||
],
|
||||
socialProviders: {
|
||||
discord: {
|
||||
clientId: process.env.DISCORD_CLIENT_ID!,
|
||||
clientSecret: process.env.DISCORD_CLIENT_SECRET!,
|
||||
},
|
||||
github: {
|
||||
clientId: process.env.GITHUB_CLIENT_ID!,
|
||||
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
|
||||
},
|
||||
},
|
||||
database: prismaAdapter(prisma, {
|
||||
provider: "postgresql"
|
||||
}),
|
||||
})
|
7
src/lib/prisma.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
import { PrismaClient } from "@prisma/client";
|
||||
|
||||
const prisma: PrismaClient = new PrismaClient({
|
||||
datasourceUrl: import.meta.env.DATABASE_URL,
|
||||
})
|
||||
|
||||
export default prisma;
|
19
src/middleware.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
import { auth } from "./lib/auth";
|
||||
import { defineMiddleware } from "astro:middleware";
|
||||
|
||||
export const onRequest = defineMiddleware(async (context, next) => {
|
||||
const isAuthed = await auth.api
|
||||
.getSession({
|
||||
headers: context.request.headers,
|
||||
})
|
||||
|
||||
if (isAuthed) {
|
||||
context.locals.user = isAuthed.user;
|
||||
context.locals.session = isAuthed.session;
|
||||
} else {
|
||||
context.locals.user = null;
|
||||
context.locals.session = null;
|
||||
}
|
||||
|
||||
return next();
|
||||
});
|
65
src/pages/account/login.astro
Normal file
|
@ -0,0 +1,65 @@
|
|||
---
|
||||
|
||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||
import HR from '../../components/HR.astro';
|
||||
import Icon from '../../components/Icon.astro';
|
||||
|
||||
---
|
||||
|
||||
<style>
|
||||
@import url('/src/styles/globals.css');
|
||||
@tailwind components;
|
||||
|
||||
@layer components {
|
||||
input[type="text"] {
|
||||
@apply ml-2 px-2 text-current bg-crusta-200 dark:bg-night-900 dark:bg-opacity-80;
|
||||
}
|
||||
.passkey {
|
||||
@apply p-2 border-2 rounded-md border-crusta-800 dark:border-night-200 bg-crusta-300 dark:bg-night-800 text-current hover:underline;
|
||||
}
|
||||
.sso {
|
||||
@apply p-2 border-2 rounded-md border-gray-600 dark:border-gray-300 text-current hover:underline;
|
||||
}
|
||||
a {
|
||||
@apply text-subtitle hover:underline;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<BaseLayout title="login">
|
||||
<h2 class="font-serif text-3xl my-2">login to eleboog.com</h2>
|
||||
<p>With an eleboog.com account, you can comment on blog posts and get access to exclusive content.</p>
|
||||
<HR/>
|
||||
<article class="space-y-2">
|
||||
<div class="mb-2">
|
||||
<button class="passkey">
|
||||
login with passkey <Icon icon="passkey" class="inline fill-current w-8 h-8" title="passkey" aira-label="passkey"/>
|
||||
</button>
|
||||
 
|
||||
<span>( <a href="/help/passkeys" class="font-mono">wait, huh?</a> )</span>
|
||||
</div>
|
||||
<!--<a class="font-serif text-sm">register with passkey</a>-->
|
||||
<p>— or —</p>
|
||||
<p>login or register with an existing account below:</p>
|
||||
<div class="space-x-4">
|
||||
<button class="sso" id="discord"><Icon icon="FaDiscord" class="inline fill-current h-8" title="discord" aria-label="discord"/> discord</button>
|
||||
<button class="sso" id="github"><Icon icon="FaGitHub" class="inline fill-current h-8" title="github" aria-label="github"/> github</button>
|
||||
<!--<button class="sso"><Icon icon="FaGoogle" class="inline fill-current h-8" title="google" aria-label="google"/> google</button>-->
|
||||
</div>
|
||||
</article>
|
||||
</BaseLayout>
|
||||
|
||||
<script>
|
||||
const { signIn, signOut } = await import("../../lib/auth-client");
|
||||
const discordBtn = document.querySelector("#discord");
|
||||
const githubBtn = document.querySelector("#github");
|
||||
|
||||
discordBtn?.addEventListener('click', () => signIn.social({
|
||||
provider: "discord",
|
||||
callbackURL: "/",
|
||||
}))
|
||||
|
||||
githubBtn?.addEventListener('click', () => signIn.social({
|
||||
provider: "github",
|
||||
callbackURL: "/",
|
||||
}))
|
||||
</script>
|
85
src/pages/account/settings/index.astro
Normal file
|
@ -0,0 +1,85 @@
|
|||
---
|
||||
export const prerender = false
|
||||
|
||||
import BaseLayout from '../../../layouts/BaseLayout.astro';
|
||||
import HR from '../../../components/HR.astro';
|
||||
import Icon from '../../../components/Icon.astro';
|
||||
|
||||
import prisma from '../../../lib/prisma';
|
||||
|
||||
import { actions } from 'astro:actions';
|
||||
|
||||
if (!Astro.locals.session || !Astro.locals.user) {
|
||||
return Astro.redirect("/account/login");
|
||||
}
|
||||
const user = Astro.locals.user
|
||||
|
||||
|
||||
if (Astro.request.method === "POST") {
|
||||
try {
|
||||
const input = await Astro.request.formData();
|
||||
const username = input.get("username");
|
||||
|
||||
const result = await prisma.user.update({
|
||||
where: {
|
||||
id: user?.id,
|
||||
},
|
||||
data: {
|
||||
username: username?.toString(),
|
||||
},
|
||||
});
|
||||
|
||||
return Astro.redirect(Astro.request.url);
|
||||
|
||||
/*const { data, error } = await Astro.callAction(actions.updateUsername, {
|
||||
id: user.id,
|
||||
username: username,
|
||||
});*/
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
console.error(error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
---
|
||||
|
||||
<style>
|
||||
@import url('/src/styles/globals.css');
|
||||
@tailwind components;
|
||||
|
||||
@layer components {
|
||||
input[type="text"] {
|
||||
@apply ml-2 px-2 text-current bg-crusta-200 dark:bg-night-900 dark:bg-opacity-80;
|
||||
}
|
||||
.passkey {
|
||||
@apply p-2 border-2 rounded-md border-crusta-800 dark:border-night-200 bg-crusta-300 dark:bg-night-800 text-current hover:underline;
|
||||
}
|
||||
.sso {
|
||||
@apply p-2 border-2 rounded-md border-gray-600 dark:border-gray-300 text-current hover:underline;
|
||||
}
|
||||
a {
|
||||
@apply text-subtitle hover:underline;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<BaseLayout title="account settings">
|
||||
<h2 class="font-serif text-3xl my-2">account settings</h2>
|
||||
<HR/>
|
||||
<article class="space-y-2">
|
||||
<h3 class="font-serif text-2xl">user info</h3>
|
||||
<h4 class="font-mono text-2xl">username</h4>
|
||||
{user.username ? <p>
|
||||
Your current username is {user.username}. You can change your username below.
|
||||
</p> : <p>
|
||||
<span class="text-red-600 dark:text-red-400">Huh, you do not seem to have a username yet. You should fix that. Like, now.</span>
|
||||
</p>}
|
||||
<form method="POST" class="flex">
|
||||
<label for="username">username:
|
||||
<input type="text" name="username" id="username" required/>
|
||||
</label>
|
||||
 
|
||||
<input type="submit" value="update" class="text-subtitle hover:underline"/>
|
||||
</form>
|
||||
</article>
|
||||
</BaseLayout>
|
6
src/pages/api/auth/[...all].ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
import { auth } from "../../../lib/auth"; // import your Better Auth instance
|
||||
import type { APIRoute } from "astro";
|
||||
|
||||
export const ALL: APIRoute = async (ctx) => {
|
||||
return auth.handler(ctx.request);
|
||||
};
|
95
src/pages/fun/mdxsheet.mdx
Normal file
|
@ -0,0 +1,95 @@
|
|||
---
|
||||
layout: '../../layouts/StandaloneMDXLayout.astro'
|
||||
title: MDX Stylesheet
|
||||
date: 1970-01-01
|
||||
summary: "A showcase of everything I can do with MDX + the addons I've installed."
|
||||
slug: mdxsheet
|
||||
draft: true
|
||||
---
|
||||
|
||||
Woah, how did you find this? You must be... a wizard or something. Or a witch. I like that idea better. You're a witch.
|
||||
|
||||
# Base Markdown (CommonMark)
|
||||
|
||||
OK, this should be fairly obvious.
|
||||
|
||||
Regular text is by default grouped into paragraphs. Each paragraph is separated by a double line break. In other words, there needs to be
|
||||
a blank line inbetween each paragraph...
|
||||
|
||||
...like I just did! If you don't have a blank line inbetween line breaks, the line break will be ignored.
|
||||
This is great if you're writing in a code editor (like I am) where there is no autowrap:
|
||||
you can just break the line within the editor...
|
||||
whenever you need to...
|
||||
so you can keep all your text in view without having to scroll horizontaly.
|
||||
|
||||
```mdx
|
||||
you can just break the line within the editor...
|
||||
whenever you need to...
|
||||
so you can keep all your text in view without having to scroll horizontaly.
|
||||
```
|
||||
|
||||
Technical note: if you break the line without a space inbetween
|
||||
the words, a space will be automatically added so you don't accidentally make a catdog word. If there *is* a space before the break, that
|
||||
space will be respected.
|
||||
|
||||
If you *want* the line break to actually happen, there are multiple ways to do it.
|
||||
The first method is to add **two or more** spaces after the line you want to break.`°°`
|
||||
The second method is to add a backslash before the line break so the break is "escaped" and isn't cut out like it usually is.`\`\
|
||||
Finally, you can just be silly and add an HTML break element.`<br/>`<br/>
|
||||
|
||||
<p class="text-subtitle text-sm mb-2">today i learned that • in Monofur is actually rendered as `•`. cute.</p>
|
||||
|
||||
Honestly, if I were to micromanage the MDX parser, i would *disable* the two-space trick and force myself to make line breaks more
|
||||
visually obvious. I hate the two-space thing. With a passion.\
|
||||
My favorite is probably the escape method followed by the HTML method.
|
||||
|
||||
You aren't limited to just `<br/>` for HTML: *any HTML elements can be inserted into your Markdown document,* and it will be passed
|
||||
through just fine.
|
||||
|
||||
```html
|
||||
<div class="border-red-500 border-2 rounded-xl border-dashed p-4 mb-2">
|
||||
<p class="mb-0 text-green-700 dark:text-green-400">hiiiii</p>
|
||||
</div>
|
||||
```
|
||||
|
||||
<div class="border-red-500 border-2 rounded-xl border-dashed p-4 mb-2">
|
||||
<p class="mb-0 text-green-700 dark:text-green-400">hiiiii</p>
|
||||
</div>
|
||||
|
||||
The classes here are [Tailwind](https://tailwindcss.com) classes so I can style individual elements however I want without having to write
|
||||
50 billion lines of CSS.
|
||||
|
||||
You may notice that I put `mb-2` for the `div` but `mb-0` for the `p`. What's up with that?\
|
||||
`p` is a default element in Markdown — every paragraph I write in Markdown is wrapped with a `p` element. Thus, I added my own CSS styling
|
||||
to `p` to make everything look nice. That includes a `mb-2` by default. This styling happens *after* all the Markdown is parsed into HTML,
|
||||
which means the explicit `p` is *also* affected.
|
||||
So, to make sure there isn't extra white space within the HTML `div`, I reset the bottom margin of the `p` to `mb-0`.
|
||||
|
||||
The CSS styling itself is done through a `markdown.css` file that automagically applies certain Tailwind classes to any elements inside an
|
||||
`article` tag, a.k.a. the entirety of the parsed Markdown.
|
||||
|
||||
```css
|
||||
article {
|
||||
//...
|
||||
p {
|
||||
@apply text-black dark:text-night-100 mb-2
|
||||
}
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
<p class="text-subtitle text-sm mb-2">looks like I need to add styling for CSS code lmao</p>
|
||||
|
||||
Fun fact: this `dark:` tag is basically how I style the entirety of the site for dark mode. It's also why, currently, dark mode is based
|
||||
on your system or browser's dark mode setting: it Just Works™ and I don't want to make it more complicated right now.
|
||||
|
||||
---
|
||||
|
||||
Headers work like you would expect.
|
||||
|
||||
# Header 1
|
||||
## Header 2
|
||||
### Header 3
|
||||
#### Header 4
|
||||
##### Header 5 - oh, i didnt make styling for this one oops
|
||||
###### Header 6 - not this one either. i'll get around to it eventually
|
180
src/pages/help/passkeys.mdx
Normal file
|
@ -0,0 +1,180 @@
|
|||
---
|
||||
title: "Wait, what's this \"login with passkey\" button?"
|
||||
date: 1970-01-01
|
||||
summary: A brief guide on how to setup passkeys for your eleboog.com account.
|
||||
layout: '../../layouts/StandaloneMDXLayout.astro'
|
||||
slug: passkeys
|
||||
draft: true
|
||||
---
|
||||
|
||||
import Icon from '../../components/Icon.astro'
|
||||
import MDXAccordion from '../../components/mdx/MDXAccordion.astro'
|
||||
|
||||
You might have noticed when trying to [login or register](/account/login) for an eleboog.com account, there is no option to input an email and
|
||||
password like most other sites, especially small ones like mine. Instead, there is this button:
|
||||
|
||||
<button class="passkey mb-2 p-2 border-2 rounded-md border-crusta-800 dark:border-night-200 bg-crusta-300 dark:bg-night-800 text-current hover:underline; hover:cursor-default">
|
||||
<span>login with passkey <Icon icon="passkey" class="inline fill-current w-8 h-8" title="passkey" aira-label="passkey"/></span>
|
||||
</button>
|
||||
|
||||
What the hell is a passkey? How do I log in with it? How do I *register* with it? Why should I use this over the Google link below it?
|
||||
All of these questions and more will be answered in this tome, so strap in. We're about to open some windows.
|
||||
|
||||
If you just want to figure out how to *use* passkeys and don't care about how it works and its limitations, check [The Table®](#the-table) to
|
||||
see if your OS and browser combination like passkeys, and if they do, follow the tutorials on [adding a passkey to your account](#registering-a-new-passkey-on-an-existing-account)
|
||||
and [logging in with it](#logging-in-with-your-passkey).
|
||||
|
||||
# what even *is* a passkey?
|
||||
|
||||
The best way to describe a passkey is a secret handshake between you and a website or service. Instead of having to remember a password
|
||||
(and making sure you don't accidentally give anyone your password, either on your own or through a security breach), your computer stores
|
||||
the credentials you need, verifies it's you before handing it out (usually with biometric authentication such as a fingerprint or FaceID), and
|
||||
then the server checks your computer's credentials with the info it has on you to make sure you are who you say you are.
|
||||
|
||||
If you want to understand the gobblygook I just threw at you, expand this dropdown for a crash course.
|
||||
|
||||
<MDXAccordion title="nerd shit">
|
||||
To distill all of that into layman's terms, let's say I want to give you a way to confirm who I am when I meet up with you, no matter where we are
|
||||
or what we are doing. A great way to do this would be to make a very special key, a key that I keep close and protect at all times. I then make
|
||||
a *shitton* of locks with this special key and give them out to all of the people I want to meet up with, including you. You now hold a special lock
|
||||
that can only be opened by my special key. When we meet up, you show me your lock, and I try to use my key to open the lock. If it opens, then
|
||||
I've confirmed that I am, in fact, me! If not, then the key that I am holding is likely a counterfit, and the person you are talking to right
|
||||
now is *not* me.
|
||||
|
||||
This is the basics of private/publickey authentication: you generate a pair of keys, one that's kept to yourself (private) and one that you give out
|
||||
to all of the people or services that you want to share your identity with (public). To authenticate yourself, the server gives you the public
|
||||
key you gave them, and you match it with your private key. If they pair up successfully, congrats, you're in! If not, then you're rejected.
|
||||
|
||||
Of course, you don't actually give the server your private key: that would be *stupidly insecure*. Instead, the server gives you a randomized
|
||||
string (a "challenge") to play with. You encrypt the string with your private key and give the string back to the server. The server then
|
||||
attempts to *de*crypt your string with the public key you gave it. If everything matches up, it should be able to decrypt the string *back*
|
||||
to its original form. Private key encrypts, public key decrypts, everybody wins.
|
||||
|
||||
Still, this leads one important question: what if your private key gets stolen? That will mean all anyone has to do is find the people who
|
||||
are holding your locks (public keys), and all of a sudden your identity has been stolen!!! Oh no!!!11!!1! However, we can prevent this by
|
||||
keeping the private key in its *own* locked box, only able to be opened by *another* security check. In computer-land, this means we *encrypt*
|
||||
the private key, and require another form of authentication to properly decrypt it. Back in the day, this was, whoops, another password (or
|
||||
"passphrase"), but for most passkey implementations, this second layer of authentication is usually some sort of biometric authenticaion.
|
||||
|
||||
So, on your iPhone for example, when you use a passkey on my site, the following (very simplified) steps happen:
|
||||
|
||||
1. The server gives a randomly generated string (a "challenge") to your browser as part of a request for your passkey.
|
||||
2. The browser passes that request to iOS, which gives you a prompt to confirm whether you want to log in with your stored passkey.
|
||||
3. Once you hit the okay button, iOS performs FaceID to authenticate you. If that succeeds, iOS is able to decrypt your private key.
|
||||
4. iOS then encrypts the challenge string with your **private** key and gives it to the browser along with the ID's of your user account and your passkey itself.
|
||||
5. The browser sends this information to the server.
|
||||
6. The server then finds the **public** key associated with your account's passkey and attempts to *de*crypt the challenge with it. If the challenge
|
||||
successfully decrypts back into its original form, then it knows your private key matched its public key and lets you in!
|
||||
</MDXAccordion>
|
||||
|
||||
## ok, why should I care?
|
||||
|
||||
Passwords suck. They suck massive booty cheeks.
|
||||
|
||||
1. You need to remember the password in order to log in. You're human. You forget things.
|
||||
2. So you write it down somewhere. Sometimes on sticky notes. Sometimes in password books. Smart people store them in password managers like
|
||||
KeePass or 1Password.
|
||||
3. But wherever you store the password, it is fucked. Write it on a sticky note? Whoever walks by your computer can now log in.
|
||||
Write it in a book? Someone finds and/or steals the book, now they know your password. Put it in a password manager? [The password manager
|
||||
gets breached and now all of your precious passwords belong to the hacker known as 4chan.](https://www.tomsguide.com/computing/password-managers/millions-stolen-from-lastpass-users-in-massive-hack-attack-what-you-need-to-know)
|
||||
4. The only way you can prevent your password from being stolen is to just... not write it down. Which means you have to remember it. Oh. We're back at the start again.
|
||||
|
||||
This isn't even getting into how the services you make passwords for can screw them up (like storing your passwords in plain text for anyone to see
|
||||
that logs into the server yaaayyyyy).
|
||||
|
||||
One solution to this is to use another, big account you already have to log into all of your new, smaller ones — this is known as Single Sign-On, or SSO for short.
|
||||
Of course, this means that the big account is *especially* important to secure as getting access to that account gives an attacker access to *all* of your accounts (tho since most people use the same email address for everything, this is already kind of a given).
|
||||
|
||||
Another solution to this is to require another method to authenticate yourself after you put in your password. This is known as Two-Factor Authentication, or 2FA for short.
|
||||
This is great as a way to make an existing system more secure, but many 2FA methods have their own problems (if you use emails, your email account could get hacked; if you use your phone, your SIM card could get hacked; if you use apps like Google Authenticator, the account holding the codes could get hacked, or if the codes are just held on your phone, your phone could get stolen or lost; etc etc etc etc etc).
|
||||
|
||||
In my opinion, passkeys are the best existing solution to this problem. 2FA is often built-in (in the form of biometric authentication required before your passkey can be used), it doesn't have to rely on
|
||||
another account or service to function properly, and *working* implementations are usually very smooth to operate, both in logging in with and registering a new passkey.
|
||||
|
||||
...of course, that leads to the biggest problem with passkeys...
|
||||
|
||||
# passkeys don't work for everyone
|
||||
|
||||
For some incredibly fucked up reason I cannot fathom why it hasn't been fixed already other than everyone being too individualistic to realize the
|
||||
potential of passkeys being a global shared system that Just Works™... Passkeys don't, in fact, Just Work™. Whether you will be able
|
||||
to use passkeys depends on three factors: your **operating system**, your **browser**, and whether you're using **third-party password managers**
|
||||
that have their own passkey support (and want to use that support to keep everything in one place).
|
||||
|
||||
## The Table®
|
||||
|
||||
The following table is (my best) catalogue of what combination of browsers and OS's currently work with passkeys, along with how easy it is to
|
||||
let third-party passkey managers work with said combination. Disclaimer: this is all in my personal experience, so there are gaps in my knowledge.
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>↓OS's<br/>→Browsers</th>
|
||||
<th>Chrome</th>
|
||||
<th>Firefox</th>
|
||||
<th>Safari</th>
|
||||
<th>Other</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Windows</td>
|
||||
<td>not sure</td>
|
||||
<td>No, requires TP extension</td>
|
||||
<td>n/a</td>
|
||||
<td>Mileage May Vary™</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>macOS</td>
|
||||
<td>No, requires TP extension</td>
|
||||
<td>No, requires TP extension</td>
|
||||
<td>Yes + TP (extensions)</td>
|
||||
<td>Mileage May Vary™</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>iOS / iPadOS</td>
|
||||
<td>n/a</td>
|
||||
<td>n/a</td>
|
||||
<td>Yes + TP (native)</td>
|
||||
<td>n/a</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Android</td>
|
||||
<td>Yes + TP (native)</td>
|
||||
<td>not sure</td>
|
||||
<td>n/a</td>
|
||||
<td>Mileage May Vary™</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Linux</td>
|
||||
<td>not sure yet</td>
|
||||
<td>not sure yet</td>
|
||||
<td>n/a</td>
|
||||
<td>Mileage May Vary™</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<MDXAccordion title="notes on passkey support">
|
||||
- Typically, the support offered on other browsers depends on what browser they're based on. Chromium browsers will act like Chrome, Firefox
|
||||
forks will act like Firefox, every browser on iOS (except in the EU) will act like Safari, etc. Still, since these browsers run on their own
|
||||
codebases forked off from the mainline codebase, updates mainstream browsers get to improve passkey support may not come to forks in a timely
|
||||
manner, if *ever* (Pale Moon, I am giving you criminal offensive side-eye).
|
||||
- Firefox seems to be allergic to passkey support. On both Windows and macOS, passkey requests just result in a "Insert authentication key"
|
||||
popup. The only way to fix this is to add an extension for a third-party passkey manager, in which case the extension will see the request and
|
||||
handle it on its own.
|
||||
- Chrome also seems to have this problem on macOS. I assume Apple isn't very concerned with making their system-level passkey manager work with
|
||||
third-party browsers.
|
||||
- On iOS, passkeys seem to be handled mostly on an OS-level basis. Since every web browser on iOS is Safari in a trenchcoat outside of the EU,
|
||||
it's possible to make all browsers, even third-party ones, just let Jesus (Tim Cook) take the wheel. This is great for ensuring passkeys work
|
||||
system-wide... but bad for other, unrelated reasons. I'm not sure if things change in the EU, since browsers are allowed to have alternative
|
||||
web engines over there (instead of being forced to use Safari's WebKit).
|
||||
- I have no fucking idea what the state of passkey support is on Linux. I am switching over to it very soon, so hopefully I find out soon too.
|
||||
I assume there are differences between major distributions, so once I start exploring Linux, I will have categores for Ubuntu, Debian,
|
||||
Fedora, Arch, and "Other Linux".
|
||||
</MDXAccordion>
|
||||
|
||||
# registering a new passkey on an existing account
|
||||
|
||||
To register a passkey on your existing account, first, [go to your account security settings](/account/settings/security).
|
||||
|
||||
|
||||
|
||||
# logging in with your passkey
|
||||
|
||||
# registering a new account with a passkey
|
|
@ -82,4 +82,16 @@ article {
|
|||
img {
|
||||
@apply relative -z-10 border-4 border-crusta-200 dark:border-night-800 rounded-lg shadow-lgr shadow-crusta-400/20 dark:shadow-night-400/50 my-2
|
||||
}
|
||||
table {
|
||||
@apply border-2 w-full border-collapse mb-2
|
||||
}
|
||||
tr {
|
||||
@apply border-2
|
||||
}
|
||||
th {
|
||||
@apply border-2
|
||||
}
|
||||
td {
|
||||
@apply border-2 text-center
|
||||
}
|
||||
}
|
1
src/svg/FaDiscord.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M524.5 69.8a1.5 1.5 0 0 0 -.8-.7A485.1 485.1 0 0 0 404.1 32a1.8 1.8 0 0 0 -1.9 .9 337.5 337.5 0 0 0 -14.9 30.6 447.8 447.8 0 0 0 -134.4 0 309.5 309.5 0 0 0 -15.1-30.6 1.9 1.9 0 0 0 -1.9-.9A483.7 483.7 0 0 0 116.1 69.1a1.7 1.7 0 0 0 -.8 .7C39.1 183.7 18.2 294.7 28.4 404.4a2 2 0 0 0 .8 1.4A487.7 487.7 0 0 0 176 479.9a1.9 1.9 0 0 0 2.1-.7A348.2 348.2 0 0 0 208.1 430.4a1.9 1.9 0 0 0 -1-2.6 321.2 321.2 0 0 1 -45.9-21.9 1.9 1.9 0 0 1 -.2-3.1c3.1-2.3 6.2-4.7 9.1-7.1a1.8 1.8 0 0 1 1.9-.3c96.2 43.9 200.4 43.9 295.5 0a1.8 1.8 0 0 1 1.9 .2c2.9 2.4 6 4.9 9.1 7.2a1.9 1.9 0 0 1 -.2 3.1 301.4 301.4 0 0 1 -45.9 21.8 1.9 1.9 0 0 0 -1 2.6 391.1 391.1 0 0 0 30 48.8 1.9 1.9 0 0 0 2.1 .7A486 486 0 0 0 610.7 405.7a1.9 1.9 0 0 0 .8-1.4C623.7 277.6 590.9 167.5 524.5 69.8zM222.5 337.6c-29 0-52.8-26.6-52.8-59.2S193.1 219.1 222.5 219.1c29.7 0 53.3 26.8 52.8 59.2C275.3 311 251.9 337.6 222.5 337.6zm195.4 0c-29 0-52.8-26.6-52.8-59.2S388.4 219.1 417.9 219.1c29.7 0 53.3 26.8 52.8 59.2C470.7 311 447.5 337.6 417.9 337.6z"/></svg>
|
After Width: | Height: | Size: 1.2 KiB |
1
src/svg/FaGitHub.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3 .3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5 .3-6.2 2.3zm44.2-1.7c-2.9 .7-4.9 2.6-4.6 4.9 .3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3 .7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3 .3 2.9 2.3 3.9 1.6 1 3.6 .7 4.3-.7 .7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3 .7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3 .7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"/></svg>
|
After Width: | Height: | Size: 1.5 KiB |
1
src/svg/FaGoogle.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 488 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M488 261.8C488 403.3 391.1 504 248 504 110.8 504 0 393.2 0 256S110.8 8 248 8c66.8 0 123 24.5 166.3 64.9l-67.5 64.9C258.5 52.6 94.3 116.6 94.3 256c0 86.5 69.1 156.6 153.7 156.6 98.2 0 135-70.4 140.8-106.9H248v-85.3h236.1c2.3 12.7 3.9 24.9 3.9 41.4z"/></svg>
|
After Width: | Height: | Size: 478 B |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |