Add rewrite
This commit is contained in:
53
server/api/rewrite.post.ts
Normal file
53
server/api/rewrite.post.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
export default defineEventHandler(async (event) => {
|
||||
const session = await requireUserSession(event);
|
||||
|
||||
const db = useDrizzle();
|
||||
|
||||
const query = getQuery(event);
|
||||
|
||||
const user = await db.query.users.findFirst({
|
||||
where: (f, o) => o.eq(f.id, session.user.id),
|
||||
with: {
|
||||
preferences: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!user?.premium) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
message: "Premium membership required.",
|
||||
});
|
||||
}
|
||||
|
||||
const activityId = (query.activity as string).replace(
|
||||
/https:\/\/(www\.)?strava\.com\/activities\//,
|
||||
"",
|
||||
);
|
||||
|
||||
const strava = await useStrava(session.user.id);
|
||||
|
||||
const activity = await strava!<any>(`/activities/${activityId}`);
|
||||
|
||||
const [aiError, stravaRequestBody] = await createActivityContent(
|
||||
activity,
|
||||
user!,
|
||||
);
|
||||
if (aiError) {
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
message: `OPENAI API: ${aiError.message}`,
|
||||
});
|
||||
}
|
||||
|
||||
await strava!(`activities/${activityId}`, {
|
||||
method: "PUT",
|
||||
body: stravaRequestBody,
|
||||
}).catch((error) => {
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
message: `Strava API: ${error.message}`,
|
||||
});
|
||||
});
|
||||
|
||||
sendNoContent(event);
|
||||
});
|
||||
1
server/auth.d.ts
vendored
1
server/auth.d.ts
vendored
@@ -16,6 +16,7 @@ declare module "#auth-utils" {
|
||||
sex: string;
|
||||
weight: number;
|
||||
avatar: string;
|
||||
premium: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
1
server/database/migrations/0001_smooth_jazinda.sql
Normal file
1
server/database/migrations/0001_smooth_jazinda.sql
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TABLE "users" ADD COLUMN "premium" boolean;
|
||||
1
server/database/migrations/0002_many_marauders.sql
Normal file
1
server/database/migrations/0002_many_marauders.sql
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TABLE "users" ALTER COLUMN "premium" SET NOT NULL;
|
||||
228
server/database/migrations/meta/0001_snapshot.json
Normal file
228
server/database/migrations/meta/0001_snapshot.json
Normal file
@@ -0,0 +1,228 @@
|
||||
{
|
||||
"id": "912c6e36-57b1-4e7f-a29b-586b187b1c32",
|
||||
"prevId": "c8519a52-b999-48f3-b532-42a5678e3905",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"public.preferences": {
|
||||
"name": "preferences",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"identity": {
|
||||
"type": "always",
|
||||
"name": "preferences_id_seq",
|
||||
"schema": "public",
|
||||
"increment": "1",
|
||||
"startWith": "1",
|
||||
"minValue": "1",
|
||||
"maxValue": "2147483647",
|
||||
"cache": "1",
|
||||
"cycle": false
|
||||
}
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"data": {
|
||||
"name": "data",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"preferences_user_id_users_id_fk": {
|
||||
"name": "preferences_user_id_users_id_fk",
|
||||
"tableFrom": "preferences",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"preferences_user_id_unique": {
|
||||
"name": "preferences_user_id_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"user_id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.tokens": {
|
||||
"name": "tokens",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"identity": {
|
||||
"type": "always",
|
||||
"name": "tokens_id_seq",
|
||||
"schema": "public",
|
||||
"increment": "1",
|
||||
"startWith": "1",
|
||||
"minValue": "1",
|
||||
"maxValue": "2147483647",
|
||||
"cache": "1",
|
||||
"cycle": false
|
||||
}
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"refresh_token": {
|
||||
"name": "refresh_token",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"access_token": {
|
||||
"name": "access_token",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"expires_at": {
|
||||
"name": "expires_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"tokens_user_id_users_id_fk": {
|
||||
"name": "tokens_user_id_users_id_fk",
|
||||
"tableFrom": "tokens",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"tokens_user_id_unique": {
|
||||
"name": "tokens_user_id_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"user_id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.users": {
|
||||
"name": "users",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"avatar": {
|
||||
"name": "avatar",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"city": {
|
||||
"name": "city",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"country": {
|
||||
"name": "country",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"sex": {
|
||||
"name": "sex",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"premium": {
|
||||
"name": "premium",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"weight": {
|
||||
"name": "weight",
|
||||
"type": "numeric",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"schemas": {},
|
||||
"sequences": {},
|
||||
"roles": {},
|
||||
"policies": {},
|
||||
"views": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
||||
228
server/database/migrations/meta/0002_snapshot.json
Normal file
228
server/database/migrations/meta/0002_snapshot.json
Normal file
@@ -0,0 +1,228 @@
|
||||
{
|
||||
"id": "7b607167-551b-4aa3-8305-afe3c4d72ace",
|
||||
"prevId": "912c6e36-57b1-4e7f-a29b-586b187b1c32",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"public.preferences": {
|
||||
"name": "preferences",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"identity": {
|
||||
"type": "always",
|
||||
"name": "preferences_id_seq",
|
||||
"schema": "public",
|
||||
"increment": "1",
|
||||
"startWith": "1",
|
||||
"minValue": "1",
|
||||
"maxValue": "2147483647",
|
||||
"cache": "1",
|
||||
"cycle": false
|
||||
}
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"data": {
|
||||
"name": "data",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"preferences_user_id_users_id_fk": {
|
||||
"name": "preferences_user_id_users_id_fk",
|
||||
"tableFrom": "preferences",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"preferences_user_id_unique": {
|
||||
"name": "preferences_user_id_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"user_id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.tokens": {
|
||||
"name": "tokens",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"identity": {
|
||||
"type": "always",
|
||||
"name": "tokens_id_seq",
|
||||
"schema": "public",
|
||||
"increment": "1",
|
||||
"startWith": "1",
|
||||
"minValue": "1",
|
||||
"maxValue": "2147483647",
|
||||
"cache": "1",
|
||||
"cycle": false
|
||||
}
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"refresh_token": {
|
||||
"name": "refresh_token",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"access_token": {
|
||||
"name": "access_token",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"expires_at": {
|
||||
"name": "expires_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"tokens_user_id_users_id_fk": {
|
||||
"name": "tokens_user_id_users_id_fk",
|
||||
"tableFrom": "tokens",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"tokens_user_id_unique": {
|
||||
"name": "tokens_user_id_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"user_id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.users": {
|
||||
"name": "users",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"avatar": {
|
||||
"name": "avatar",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"city": {
|
||||
"name": "city",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"country": {
|
||||
"name": "country",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"sex": {
|
||||
"name": "sex",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"premium": {
|
||||
"name": "premium",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"weight": {
|
||||
"name": "weight",
|
||||
"type": "numeric",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"schemas": {},
|
||||
"sequences": {},
|
||||
"roles": {},
|
||||
"policies": {},
|
||||
"views": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,20 @@
|
||||
"when": 1745335305323,
|
||||
"tag": "0000_slim_blonde_phantom",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 1,
|
||||
"version": "7",
|
||||
"when": 1747547391071,
|
||||
"tag": "0001_smooth_jazinda",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 2,
|
||||
"version": "7",
|
||||
"when": 1747564780270,
|
||||
"tag": "0002_many_marauders",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
numeric,
|
||||
timestamp,
|
||||
jsonb,
|
||||
boolean,
|
||||
} from "drizzle-orm/pg-core";
|
||||
|
||||
export const users = pgTable("users", {
|
||||
@@ -15,6 +16,9 @@ export const users = pgTable("users", {
|
||||
city: text("city"),
|
||||
country: text("country"),
|
||||
sex: text("sex"),
|
||||
premium: boolean("premium")
|
||||
.notNull()
|
||||
.$defaultFn(() => false),
|
||||
weight: numeric("weight", {
|
||||
mode: "number",
|
||||
}),
|
||||
|
||||
@@ -32,6 +32,7 @@ export default defineOAuthStravaEventHandler({
|
||||
sex: auth.user.sex,
|
||||
weight: auth.user.weight,
|
||||
avatar: auth.user.profile,
|
||||
premium: false,
|
||||
};
|
||||
|
||||
const db = useDrizzle();
|
||||
@@ -41,7 +42,7 @@ export default defineOAuthStravaEventHandler({
|
||||
.values(userPayload)
|
||||
.onConflictDoUpdate({
|
||||
target: tables.users.id,
|
||||
set: omit(userPayload, ["id"]),
|
||||
set: omit(userPayload, ["id", "premium"]),
|
||||
})
|
||||
.returning();
|
||||
|
||||
@@ -77,7 +78,7 @@ export default defineOAuthStravaEventHandler({
|
||||
.onConflictDoNothing();
|
||||
|
||||
await setUserSession(event, {
|
||||
user: userPayload,
|
||||
user: { ...userPayload, premium: user.premium },
|
||||
});
|
||||
|
||||
sendRedirect(event, "/");
|
||||
|
||||
@@ -20,7 +20,10 @@ export default defineEventHandler(async (event) => {
|
||||
|
||||
const activity = await strava!<any>(`/activities/${body.object_id}`);
|
||||
|
||||
const [aiError, aiResponse] = await createActivityContent(activity, user);
|
||||
const [aiError, stravaRequestBody] = await createActivityContent(
|
||||
activity,
|
||||
user,
|
||||
);
|
||||
if (aiError) {
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
@@ -28,20 +31,6 @@ export default defineEventHandler(async (event) => {
|
||||
});
|
||||
}
|
||||
|
||||
const responseObject = JSON.parse(
|
||||
get(aiResponse, "output.0.content.0.text"),
|
||||
) as {
|
||||
title: string;
|
||||
description: string;
|
||||
};
|
||||
|
||||
const promo = "Written by https://ghostwriter.rocks 👻";
|
||||
|
||||
const stravaRequestBody = {
|
||||
name: responseObject.title,
|
||||
description: [responseObject.description, promo].join("\n"),
|
||||
};
|
||||
|
||||
await strava!(`activities/${body.object_id}`, {
|
||||
method: "PUT",
|
||||
body: stravaRequestBody,
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { chain, draw, omit } from "radash";
|
||||
import { chain, draw, get, omit, tryit } from "radash";
|
||||
import { safeDestr } from "destr";
|
||||
import { User } from "./drizzle";
|
||||
|
||||
const promo = "Written by https://ghostwriter.rocks 👻";
|
||||
|
||||
type Activity = Record<string, any>;
|
||||
|
||||
const movingActivityTypes = [
|
||||
@@ -116,7 +119,13 @@ export const createActivityContent = async (
|
||||
"snarky",
|
||||
]);
|
||||
|
||||
const length = draw(["short", "short", "short", "medium", "a-little-more-than-medium"]);
|
||||
const length = draw([
|
||||
"short",
|
||||
"short",
|
||||
"short",
|
||||
"medium",
|
||||
"a-little-more-than-medium",
|
||||
]);
|
||||
|
||||
const prompt = `
|
||||
Generate a short title and a ${length}-lengthed description for my strava activity. Use my preferred language and unit system.
|
||||
@@ -168,5 +177,17 @@ export const createActivityContent = async (
|
||||
},
|
||||
});
|
||||
|
||||
return [aiError, aiResponse] as const;
|
||||
const [parseError, responseObject] = tryit(
|
||||
chain(
|
||||
(r) => get(r, "output.0.content.0.text"),
|
||||
(r) => safeDestr<{ title: string; description: string }>(r),
|
||||
),
|
||||
)(aiResponse);
|
||||
|
||||
const stravaRequestBody = {
|
||||
name: responseObject!.title,
|
||||
description: [responseObject!.description, promo].join("\n"),
|
||||
};
|
||||
|
||||
return [aiError || parseError, stravaRequestBody] as const;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user