feat: basic nostr-settings profile section

This commit is contained in:
MTG2000
2022-07-28 14:14:53 +03:00
parent 4904f492a1
commit b28de208d5
17 changed files with 172 additions and 23 deletions

View File

@@ -8,6 +8,7 @@ const getUserByPubKey = (pubKey) => {
})
}
module.exports = {
getUserByPubKey,
}

View File

@@ -198,6 +198,8 @@ export interface NexusGenObjects {
linkedin?: string | null; // String
location?: string | null; // String
name: string; // String!
nostr_prv_key?: string | null; // String
nostr_pub_key?: string | null; // String
role?: string | null; // String
twitter?: string | null; // String
website?: string | null; // String
@@ -405,6 +407,8 @@ export interface NexusGenFieldTypes {
linkedin: string | null; // String
location: string | null; // String
name: string; // String!
nostr_prv_key: string | null; // String
nostr_pub_key: string | null; // String
role: string | null; // String
stories: NexusGenRootTypes['Story'][]; // [Story!]!
twitter: string | null; // String
@@ -611,6 +615,8 @@ export interface NexusGenFieldTypeNames {
linkedin: 'String'
location: 'String'
name: 'String'
nostr_prv_key: 'String'
nostr_pub_key: 'String'
role: 'String'
stories: 'Story'
twitter: 'String'

View File

@@ -242,6 +242,8 @@ type User {
linkedin: String
location: String
name: String!
nostr_prv_key: String
nostr_pub_key: String
role: String
stories: [Story!]!
twitter: String

View File

@@ -23,6 +23,8 @@ const User = objectType({
t.string('linkedin')
t.string('bio')
t.string('location')
t.string('nostr_prv_key')
t.string('nostr_pub_key')
t.nonNull.list.nonNull.field('stories', {
type: "Story",
@@ -55,10 +57,15 @@ const profile = extendType({
args: {
id: nonNull(intArg())
},
async resolve(parent, { id }) {
return prisma.user.findFirst({
where: { id }
})
async resolve(parent, { id }, ctx) {
const user = await getUserByPubKey(ctx.userPubKey);
const isSelf = user?.id === id;
const profile = await prisma.user.findFirst({
where: { id },
});
if (!isSelf)
profile.nostr_prv_key = null;
return profile;
}
})
}

View File

@@ -7,6 +7,7 @@ const { createExpressApp } = require('../../modules');
const express = require('express');
const jose = require('jose');
const { JWT_SECRET } = require('../../utils/consts');
const { generatePrivateKey, getPublicKey } = require('../../utils/nostr-tools');
@@ -28,11 +29,17 @@ const loginHandler = async (req, res) => {
//Create user if not already existing
const user = await prisma.user.findFirst({ where: { pubKey: key } })
if (user === null) {
const nostr_prv_key = generatePrivateKey();
const nostr_pub_key = getPublicKey(nostr_prv_key);
await prisma.user.create({
data: {
pubKey: key,
name: key,
avatar: `https://avatars.dicebear.com/api/bottts/${key}.svg`
avatar: `https://avatars.dicebear.com/api/bottts/${key}.svg`,
nostr_prv_key,
nostr_pub_key,
}
})
}

View File

@@ -1,4 +1,5 @@
const { PrismaClient } = require("@prisma/client");
const { generatePrivateKey, getPublicKey } = require("../../api/utils/nostr-tools");
const { categories, projects } = require("./data");
@@ -14,6 +15,29 @@ async function purge() {
}
async function generateNostrKeys() {
const allUsers = await prisma.user.findMany({
where: {
nostr_prv_key: null
}
})
for (const user of allUsers) {
const prvkey = generatePrivateKey();
const pubkey = getPublicKey(prvkey);
await prisma.user.update({
where: {
id: user.id,
},
data: {
nostr_prv_key: prvkey,
nostr_pub_key: pubkey
}
})
}
}
async function main() {

View File

@@ -9,16 +9,10 @@ type Author = NonNullable<Comment['author']>
const pool = relayPool();
const RELAYS = [
'wss://nostr.drss.io',
'wss://nostr-relay.freeberty.net',
'wss://nostr.unknown.place',
'wss://nostr-relay.untethr.me',
'wss://relay.damus.io'
];
export function connect() {
RELAYS.forEach(url => {
CONSTS.DEFAULT_RELAYS.forEach(url => {
pool.addRelay(url, { read: true, write: true })
})
pool.onNotice((notice: string, relay: any) => {
@@ -99,7 +93,7 @@ export async function post({ content, filter, parentId }: {
const tags = [];
tags.push(['r', filter]);
if (parentId)
tags.push(['e', `${parentId} ${RELAYS[0]} reply`])
tags.push(['e', `${parentId} ${CONSTS.DEFAULT_RELAYS[0]} reply`])
let event: NostrEvent;
try {

View File

@@ -16,7 +16,6 @@ import styles from './styles.module.scss'
export default function PostDetailsPage() {
const { type: _type, id } = useParams();
const type = capitalize(_type);

View File

@@ -3,7 +3,6 @@ import { User } from "src/graphql"
import { trimText, withHttp } from "src/utils/helperFunctions"
import { FiGithub, FiGlobe, FiLinkedin, FiTwitter } from 'react-icons/fi'
import Button from "src/Components/Button/Button";
import { useState } from "react";
import { useToggle } from "@react-hookz/web";
import UpdateAboutForm from "./UpdateAboutForm";

View File

@@ -0,0 +1,20 @@
import { ComponentStory, ComponentMeta } from '@storybook/react';
import { MOCK_DATA } from 'src/mocks/data';
import CommentsSettingsCard from './CommentsSettingsCard';
export default {
title: 'Profiles/Profile Page/Comments Settings Card',
component: CommentsSettingsCard,
argTypes: {
backgroundColor: { control: 'color' },
},
} as ComponentMeta<typeof CommentsSettingsCard>;
const Template: ComponentStory<typeof CommentsSettingsCard> = (args) => <CommentsSettingsCard {...args} ></CommentsSettingsCard>
export const Default = Template.bind({});
Default.args = {
}

View File

@@ -0,0 +1,64 @@
import { useToggle } from '@react-hookz/web'
import { Nullable } from 'remirror';
import Button from 'src/Components/Button/Button';
import { CONSTS } from 'src/utils';
interface Props {
isOwner?: boolean;
nostr_pub_key: Nullable<string>;
nostr_prv_key: Nullable<string>;
}
export default function CommentsSettingsCard({ nostr_prv_key, nostr_pub_key, isOwner }: Props) {
return (
<div className="rounded-16 bg-white border-2 border-gray-200 p-24">
<p className="text-body2 font-bold">Nostr Settings (experimental)</p>
<p className="mt-8 text-body4 text-gray-600">
Our commenting system is experimental and uses Nostr to store and relay your messages and replies to our own relay, as well as relays ran by other people in the community.
We generate Nostr keys for you since there are no popular wallets which support it.
</p>
{nostr_prv_key && <>
<p className="text-body4 font-bold mt-24">
Your Nostr Private Key
</p>
<div className="input-wrapper mt-8 relative">
<input
type={'text'}
className="input-text"
value={nostr_prv_key}
/>
</div>
</>}
<p className="text-body4 font-bold mt-24">
Your Nostr Public Key
</p>
<div className="input-wrapper mt-8 relative">
<input
type='text'
className="input-text"
value={nostr_pub_key!}
/>
</div>
<p className="text-body4 font-bold mt-24">
Connect your Nostr identity
</p>
<div className="mt-8 py-12 relative">
<p className="text-body4 text-gray-400 font-bold">
Coming Soon 🚧
</p>
</div>
<p className="text-body4 font-bold mt-24">
Connected Relays
</p>
<ul className="mt-8 relative flex flex-col gap-8">
{CONSTS.DEFAULT_RELAYS.map((url, idx) => <li key={idx} className="text-body4 border-b py-12 px-16 bg-gray-100 border-2 border-gray-200 rounded-16">{url}</li>)}
</ul>
</div>
)
}

View File

@@ -7,6 +7,7 @@ import { Helmet } from 'react-helmet'
import { useAppSelector } from 'src/utils/hooks';
import styles from './styles.module.scss'
import StoriesCard from "./StoriesCard/StoriesCard"
import CommentsSettingsCard from "./CommentsSettingsCard/CommentsSettingsCard"
export default function ProfilePage() {
@@ -43,6 +44,10 @@ export default function ProfilePage() {
<main className="flex flex-col gap-24">
<AboutCard user={profileQuery.data.profile} isOwner={isOwner} />
<StoriesCard stories={profileQuery.data.profile.stories} isOwner={isOwner} />
{
isOwner &&
<CommentsSettingsCard nostr_prv_key={profileQuery.data.profile.nostr_prv_key} nostr_pub_key={profileQuery.data.profile.nostr_pub_key} isOwner={isOwner} />
}
</main>
<aside></aside>
</div>

View File

@@ -24,5 +24,7 @@ query profile($profileId: Int!) {
icon
}
}
nostr_prv_key
nostr_pub_key
}
}

View File

@@ -329,6 +329,7 @@ export type Story = PostBase & {
__typename?: 'Story';
author: Author;
body: Scalars['String'];
comments: Array<PostComment>;
cover_image: Maybe<Scalars['String']>;
createdAt: Scalars['Date'];
excerpt: Scalars['String'];
@@ -386,6 +387,8 @@ export type User = {
linkedin: Maybe<Scalars['String']>;
location: Maybe<Scalars['String']>;
name: Scalars['String'];
nostr_prv_key: Maybe<Scalars['String']>;
nostr_pub_key: Maybe<Scalars['String']>;
role: Maybe<Scalars['String']>;
stories: Array<Story>;
twitter: Maybe<Scalars['String']>;
@@ -516,7 +519,7 @@ export type ProfileQueryVariables = Exact<{
}>;
export type ProfileQuery = { __typename?: 'Query', profile: { __typename?: 'User', id: number, name: string, avatar: string, join_date: any, role: string | null, email: string | null, jobTitle: string | null, lightning_address: string | null, website: string | null, twitter: string | null, github: string | null, linkedin: string | null, bio: string | null, location: string | null, stories: Array<{ __typename?: 'Story', id: number, title: string, createdAt: any, tags: Array<{ __typename?: 'Tag', id: number, title: string, icon: string | null }> }> } | null };
export type ProfileQuery = { __typename?: 'Query', profile: { __typename?: 'User', id: number, name: string, avatar: string, join_date: any, role: string | null, email: string | null, jobTitle: string | null, lightning_address: string | null, website: string | null, twitter: string | null, github: string | null, linkedin: string | null, bio: string | null, location: string | null, nostr_prv_key: string | null, nostr_pub_key: string | null, stories: Array<{ __typename?: 'Story', id: number, title: string, createdAt: any, tags: Array<{ __typename?: 'Tag', id: number, title: string, icon: string | null }> }> } | null };
export type UpdateProfileAboutMutationVariables = Exact<{
data: InputMaybe<UpdateProfileInput>;
@@ -1328,6 +1331,8 @@ export const ProfileDocument = gql`
icon
}
}
nostr_prv_key
nostr_pub_key
}
}
`;

View File

@@ -16,5 +16,7 @@ export const user: User = {
role: "user",
twitter: "john-doe",
website: "https://mtg-dev.tech",
stories: posts.stories
stories: posts.stories,
nostr_prv_key: "123123124asdfsadfsa8d7fsadfasdf",
nostr_pub_key: "123124123123dfsadfsa8d7f11sadfasdf",
}

View File

@@ -1,6 +1,16 @@
const DEFAULT_RELAYS = [
'wss://nostr.drss.io',
'wss://nostr-relay.freeberty.net',
'wss://nostr.unknown.place',
'wss://nostr-relay.untethr.me',
'wss://relay.damus.io'
];
const CONSTS = {
apiEndpoint: process.env.REACT_APP_API_END_POINT ?? '/.netlify/functions',
defaultLightningAddress: 'johns@getalby.com'
defaultLightningAddress: 'johns@getalby.com',
DEFAULT_RELAYS
}
export default CONSTS;

View File

@@ -20,10 +20,12 @@ export const FallbackProvider: React.FC<React.PropsWithChildren<FabllbackProvide
const [fallback, setFallback] = React.useState<FallbackType>(null);
const updateFallback = React.useCallback((fallback: FallbackType) => {
setFallback(() => <>
setFallback(() =>
<>
<LoadingPage />
{fallback}
</>);
</>
);
}, []);
const renderChildren = React.useMemo(() => {