mirror of
https://github.com/aljazceru/landscape-template.git
synced 2025-12-17 06:14:27 +01:00
chore: remove unused files & parts
This commit is contained in:
49
README.md
49
README.md
@@ -1,6 +1,6 @@
|
||||
# Makers.Bolt.Fun
|
||||
# Lightning Landscape
|
||||
|
||||
A lightning apps directory made for and by the bitcoin community.
|
||||
A directory for lightning startups, projects, and companies.
|
||||
|
||||
## Environment Setup
|
||||
|
||||
@@ -25,48 +25,3 @@ This will spin up a local mocks server with mock data, so you can use the app of
|
||||
This will assume that you have a local api server running on port 8888, and will connect to it.
|
||||
|
||||
In all cases, the application will be running on http://localhost:3000
|
||||
|
||||
# Backend API
|
||||
|
||||
We are using serverless functions to serve our GraphQl endpoint to the client app.
|
||||
|
||||
## Running locally
|
||||
|
||||
To run the project locally with your own local DB, you will need to first put a few env variables in an env file that should be created in /envs/server directory, named `local.env`
|
||||
|
||||
The required variables that needs to be put there are:
|
||||
|
||||
```
|
||||
NODE_ENV = "development"
|
||||
DATABASE_PROXY_URL = "YOUR DB CONNECTION STRING"
|
||||
JWT_SECRET = "SOME RANDOM JWT SECRET"
|
||||
LNURL_AUTH_HOST = "http://localhost:8888/dev/login"
|
||||
CLOUDFLARE_IMAGE_ACCOUNT_ID = "FOR UPLOADING IMAGES"
|
||||
CLOUDFLARE_IMAGE_API_KEY = "FOR UPLOADING IMAGES"
|
||||
CLOUDFLARE_IMAGE_ACCOUNT_HASH = "FOR UPLOADING IMAGES"
|
||||
```
|
||||
|
||||
Then you need to run the migrations against your database.
|
||||
use the command:
|
||||
|
||||
### `npm run db:migrate-dev`
|
||||
|
||||
Finally, you can start the serverless functions using the command:
|
||||
|
||||
### `npm run server:dev`
|
||||
|
||||
And your functions will be served on http://localhost:8888/dev/graphql
|
||||
|
||||
## Database
|
||||
|
||||
`prisma studio`
|
||||
|
||||
prisma studio runs an UI for the DB
|
||||
|
||||
`prisma migrate dev`
|
||||
|
||||
Create a migration from the schema.prisma file
|
||||
|
||||
`prisma migrate deploy`
|
||||
|
||||
Apply pending migrations to the database
|
||||
|
||||
@@ -1,132 +0,0 @@
|
||||
const lnurl = require('lnurl')
|
||||
const crypto = require('crypto')
|
||||
const { prisma } = require('../../prisma')
|
||||
const { CONSTS } = require('../../utils')
|
||||
|
||||
async function generateK1() {
|
||||
let k1 = null
|
||||
const maxAttempts = 5
|
||||
let attempt = 0
|
||||
while (k1 === null && attempt < maxAttempts) {
|
||||
k1 = crypto.randomBytes(32).toString('hex')
|
||||
const hash = createHash(k1)
|
||||
const isUsed = await isHashUsed(hash);
|
||||
if (isUsed) {
|
||||
k1 = null
|
||||
}
|
||||
attempt++
|
||||
}
|
||||
if (!k1) {
|
||||
const message = 'Too many failed attempts to generate unique k1'
|
||||
throw new Error(message)
|
||||
}
|
||||
return k1
|
||||
}
|
||||
|
||||
function isHashUsed(hash) {
|
||||
return prisma.generatedK1.findFirst({ where: { value: hash } })
|
||||
}
|
||||
|
||||
function addHash(hash) {
|
||||
return prisma.generatedK1.create({
|
||||
data: {
|
||||
value: hash,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function removeHash(hash) {
|
||||
return prisma.generatedK1.delete({
|
||||
where: {
|
||||
value: hash,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function removeExpiredHashes() {
|
||||
const now = new Date();
|
||||
const lastHourDate = new Date(now.getFullYear(), now.getMonth(), now.getDate(), now.getHours(), now.getMinutes() - 10);
|
||||
|
||||
return prisma.generatedK1.deleteMany({
|
||||
where: {
|
||||
createdAt: {
|
||||
lt: lastHourDate
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
async function generateAuthUrl(options) {
|
||||
const hostname = CONSTS.LNURL_AUTH_HOST ?? 'https://auth.bolt.fun/.netlify/functions/login';
|
||||
const secret = await generateK1();
|
||||
const hash = createHash(secret);
|
||||
await addHash(hash)
|
||||
let url = `${hostname}?tag=login&k1=${secret}`
|
||||
if (options.user_token) {
|
||||
url = url + `&action=link&user_token=${options.user_token}`
|
||||
}
|
||||
return {
|
||||
url,
|
||||
encoded: lnurl.encode(url).toUpperCase(),
|
||||
secret,
|
||||
secretHash: hash,
|
||||
}
|
||||
}
|
||||
|
||||
async function getAuthTokenByHash(hash) {
|
||||
const data = await prisma.generatedK1.findFirst({
|
||||
where: {
|
||||
value: hash,
|
||||
}
|
||||
});
|
||||
return data.sid;
|
||||
}
|
||||
|
||||
function associateTokenToHash(hash, token) {
|
||||
return prisma.generatedK1.update({
|
||||
where: {
|
||||
value: hash
|
||||
},
|
||||
data: {
|
||||
sid: token
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function verifySig(sig, k1, key) {
|
||||
if (!lnurl.verifyAuthorizationSignature(sig, k1, key)) {
|
||||
const message = 'Signature verification failed'
|
||||
throw new Error(message)
|
||||
}
|
||||
const hash = createHash(k1)
|
||||
const hashExist = await isHashUsed(hash);
|
||||
if (!hashExist)
|
||||
throw new Error('Provided k1 is not issued by server')
|
||||
return { key, hash }
|
||||
}
|
||||
|
||||
function createHash(data) {
|
||||
if (!(typeof data === 'string' || Buffer.isBuffer(data))) {
|
||||
throw new Error(
|
||||
JSON.stringify({ status: 'ERROR', reason: 'Secret must be a string or a Buffer' })
|
||||
)
|
||||
}
|
||||
if (typeof data === 'string') {
|
||||
data = Buffer.from(data, 'hex')
|
||||
}
|
||||
return crypto.createHash('sha256').update(data).digest('hex')
|
||||
}
|
||||
|
||||
|
||||
|
||||
module.exports = {
|
||||
generateAuthUrl: generateAuthUrl,
|
||||
verifySig: verifySig,
|
||||
removeHash: removeHash,
|
||||
createHash: createHash,
|
||||
removeExpiredHashes: removeExpiredHashes,
|
||||
getAuthTokenByHash: getAuthTokenByHash,
|
||||
associateTokenToHash: associateTokenToHash
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
|
||||
const { prisma } = require('../../prisma')
|
||||
|
||||
const getUserByPubKey = (pubKey) => {
|
||||
if (!pubKey) return null;
|
||||
return prisma.userKey.findUnique({
|
||||
where: {
|
||||
key: pubKey
|
||||
},
|
||||
}).user()
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
getUserByPubKey,
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
|
||||
const LnurlAuthService = require('../../auth/services/lnurlAuth.service')
|
||||
const serverless = require('serverless-http');
|
||||
const { createExpressApp } = require('../../modules');
|
||||
const express = require('express');
|
||||
const jose = require('jose');
|
||||
const { JWT_SECRET } = require('../../utils/consts');
|
||||
const extractKeyFromCookie = require('../../utils/extractKeyFromCookie');
|
||||
const { getUserByPubKey } = require('../../auth/utils/helperFuncs');
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const getLoginUrl = async (req, res) => {
|
||||
|
||||
const { action } = req.query;
|
||||
|
||||
try {
|
||||
|
||||
let user_token = null;
|
||||
if (action === 'link') {
|
||||
const userPubKey = await extractKeyFromCookie(req.headers.cookie ?? req.headers.Cookie)
|
||||
const user = await getUserByPubKey(userPubKey);
|
||||
|
||||
if (!user)
|
||||
return res.status(400).json({ status: 'ERROR', reason: 'Only authenticated user can request a linking URL' });
|
||||
|
||||
user_token = await new jose.SignJWT({ user_id: user.id })
|
||||
.setProtectedHeader({ alg: 'HS256' })
|
||||
.setIssuedAt()
|
||||
.setExpirationTime('5min')
|
||||
.sign(Buffer.from(JWT_SECRET, 'utf-8'))
|
||||
}
|
||||
|
||||
const data = await LnurlAuthService.generateAuthUrl({ user_token });
|
||||
|
||||
const session_token = await new jose.SignJWT({ hash: data.secretHash })
|
||||
.setProtectedHeader({ alg: 'HS256' })
|
||||
.setIssuedAt()
|
||||
.setExpirationTime('5min')
|
||||
.sign(Buffer.from(JWT_SECRET, 'utf-8'))
|
||||
|
||||
return res
|
||||
.status(200)
|
||||
.json({ ...data, session_token });
|
||||
} catch (error) {
|
||||
res.status(500).send("Unexpected error happened, please try again")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
let app;
|
||||
|
||||
if (process.env.LOCAL) {
|
||||
app = createExpressApp()
|
||||
app.get('/get-login-url', getLoginUrl);
|
||||
}
|
||||
else {
|
||||
const router = express.Router();
|
||||
router.get('/get-login-url', getLoginUrl)
|
||||
app = createExpressApp(router)
|
||||
}
|
||||
|
||||
|
||||
const handler = serverless(app);
|
||||
exports.handler = async (event, context) => {
|
||||
return await handler(event, context);
|
||||
};
|
||||
@@ -1,40 +0,0 @@
|
||||
const { ApolloServer } = require("apollo-server-lambda");
|
||||
const schema = require('./schema')
|
||||
const extractKeyFromCookie = require("../../utils/extractKeyFromCookie");
|
||||
|
||||
|
||||
|
||||
const server = new ApolloServer({
|
||||
schema,
|
||||
context: async ({ event }) => {
|
||||
const userPubKey = await extractKeyFromCookie(event.headers.cookie ?? event.headers.Cookie)
|
||||
return { userPubKey }
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
|
||||
const apolloHandler = server.createHandler({
|
||||
expressGetMiddlewareOptions: {
|
||||
cors: {
|
||||
origin: ['http://localhost:3000', 'https://studio.apollographql.com'],
|
||||
credentials: true,
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// https://github.com/vendia/serverless-express/issues/427#issuecomment-924580007
|
||||
const handler = (event, context, ...args) => {
|
||||
|
||||
return apolloHandler(
|
||||
{
|
||||
...event,
|
||||
requestContext: context,
|
||||
},
|
||||
context,
|
||||
...args
|
||||
);
|
||||
};
|
||||
|
||||
exports.handler = handler;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,602 +0,0 @@
|
||||
### This file was generated by Nexus Schema
|
||||
### Do not make changes to this file directly
|
||||
|
||||
|
||||
type Author {
|
||||
avatar: String!
|
||||
id: Int!
|
||||
join_date: Date!
|
||||
lightning_address: String
|
||||
name: String!
|
||||
}
|
||||
|
||||
type Award {
|
||||
id: Int!
|
||||
image: String!
|
||||
project: Project!
|
||||
title: String!
|
||||
url: String!
|
||||
}
|
||||
|
||||
interface BaseUser {
|
||||
avatar: String!
|
||||
bio: String
|
||||
discord: String
|
||||
github: String
|
||||
id: Int!
|
||||
in_tournament(id: Int!): Boolean!
|
||||
jobTitle: String
|
||||
join_date: Date!
|
||||
lightning_address: String
|
||||
linkedin: String
|
||||
location: String
|
||||
name: String!
|
||||
projects: [Project!]!
|
||||
role: String
|
||||
roles: [MakerRole!]!
|
||||
similar_makers: [User!]!
|
||||
skills: [MakerSkill!]!
|
||||
stories: [Story!]!
|
||||
tournaments: [Tournament!]!
|
||||
twitter: String
|
||||
website: String
|
||||
}
|
||||
|
||||
type Bounty implements PostBase {
|
||||
applicants_count: Int!
|
||||
applications: [BountyApplication!]!
|
||||
author: Author!
|
||||
body: String!
|
||||
cover_image: String
|
||||
createdAt: Date!
|
||||
deadline: String!
|
||||
excerpt: String!
|
||||
id: Int!
|
||||
is_published: Boolean
|
||||
reward_amount: Int!
|
||||
tags: [Tag!]!
|
||||
title: String!
|
||||
type: String!
|
||||
updatedAt: Date!
|
||||
votes_count: Int!
|
||||
}
|
||||
|
||||
type BountyApplication {
|
||||
author: Author!
|
||||
date: String!
|
||||
id: Int!
|
||||
workplan: String!
|
||||
}
|
||||
|
||||
type Capability {
|
||||
icon: String!
|
||||
id: Int!
|
||||
title: String!
|
||||
}
|
||||
|
||||
type Category {
|
||||
apps_count: Int!
|
||||
cover_image: String
|
||||
icon: String
|
||||
id: Int!
|
||||
project: [Project!]!
|
||||
title: String!
|
||||
votes_sum: Int!
|
||||
}
|
||||
|
||||
input CreateProjectInput {
|
||||
capabilities: [Int!]!
|
||||
category_id: Int!
|
||||
cover_image: ImageInput!
|
||||
description: String!
|
||||
discord: String
|
||||
github: String
|
||||
hashtag: String!
|
||||
id: Int
|
||||
launch_status: ProjectLaunchStatusEnum!
|
||||
lightning_address: String
|
||||
members: [TeamMemberInput!]!
|
||||
recruit_roles: [Int!]!
|
||||
screenshots: [ImageInput!]!
|
||||
slack: String
|
||||
tagline: String!
|
||||
telegram: String
|
||||
thumbnail_image: ImageInput!
|
||||
title: String!
|
||||
tournaments: [Int!]!
|
||||
twitter: String
|
||||
website: String!
|
||||
}
|
||||
|
||||
type CreateProjectResponse {
|
||||
project: Project!
|
||||
}
|
||||
|
||||
"""Date custom scalar type"""
|
||||
scalar Date
|
||||
|
||||
type Donation {
|
||||
amount: Int!
|
||||
by: User
|
||||
createdAt: Date!
|
||||
id: Int!
|
||||
paid: Boolean!
|
||||
payment_hash: String!
|
||||
payment_request: String!
|
||||
}
|
||||
|
||||
type DonationsStats {
|
||||
applications: String!
|
||||
donations: String!
|
||||
prizes: String!
|
||||
touranments: String!
|
||||
}
|
||||
|
||||
type GenericMakerRole {
|
||||
icon: String!
|
||||
id: Int!
|
||||
title: String!
|
||||
}
|
||||
|
||||
type Hackathon {
|
||||
cover_image: String!
|
||||
description: String!
|
||||
end_date: Date!
|
||||
id: Int!
|
||||
location: String!
|
||||
start_date: Date!
|
||||
tags: [Tag!]!
|
||||
title: String!
|
||||
website: String!
|
||||
}
|
||||
|
||||
input ImageInput {
|
||||
id: String
|
||||
name: String
|
||||
url: String!
|
||||
}
|
||||
|
||||
type LnurlDetails {
|
||||
commentAllowed: Int
|
||||
maxSendable: Int
|
||||
metadata: String
|
||||
minSendable: Int
|
||||
}
|
||||
|
||||
type MakerRole {
|
||||
icon: String!
|
||||
id: Int!
|
||||
level: RoleLevelEnum!
|
||||
title: String!
|
||||
}
|
||||
|
||||
input MakerRoleInput {
|
||||
id: Int!
|
||||
level: RoleLevelEnum!
|
||||
}
|
||||
|
||||
type MakerSkill {
|
||||
id: Int!
|
||||
title: String!
|
||||
}
|
||||
|
||||
input MakerSkillInput {
|
||||
id: Int!
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
confirmDonation(payment_request: String!, preimage: String!): Donation!
|
||||
confirmVote(payment_request: String!, preimage: String!): Vote!
|
||||
createProject(input: CreateProjectInput): CreateProjectResponse
|
||||
createStory(data: StoryInputType): Story
|
||||
deleteProject(id: Int!): Project
|
||||
deleteStory(id: Int!): Story
|
||||
donate(amount_in_sat: Int!): Donation!
|
||||
registerInTournament(data: RegisterInTournamentInput, tournament_id: Int!): User
|
||||
updateProfileDetails(data: ProfileDetailsInput): MyProfile
|
||||
updateProfileRoles(data: ProfileRolesInput): MyProfile
|
||||
updateProject(input: UpdateProjectInput): CreateProjectResponse
|
||||
updateTournamentRegistration(data: UpdateTournamentRegistrationInput, tournament_id: Int!): ParticipationInfo
|
||||
updateUserPreferences(userKeys: [UserKeyInputType!]): MyProfile!
|
||||
vote(amount_in_sat: Int!, item_id: Int!, item_type: VOTE_ITEM_TYPE!): Vote!
|
||||
}
|
||||
|
||||
type MyProfile implements BaseUser {
|
||||
avatar: String!
|
||||
bio: String
|
||||
discord: String
|
||||
email: String
|
||||
github: String
|
||||
id: Int!
|
||||
in_tournament(id: Int!): Boolean!
|
||||
jobTitle: String
|
||||
join_date: Date!
|
||||
lightning_address: String
|
||||
linkedin: String
|
||||
location: String
|
||||
name: String!
|
||||
nostr_prv_key: String
|
||||
nostr_pub_key: String
|
||||
projects: [Project!]!
|
||||
role: String
|
||||
roles: [MakerRole!]!
|
||||
similar_makers: [User!]!
|
||||
skills: [MakerSkill!]!
|
||||
stories: [Story!]!
|
||||
tournaments: [Tournament!]!
|
||||
twitter: String
|
||||
walletsKeys: [WalletKey!]!
|
||||
website: String
|
||||
}
|
||||
|
||||
enum POST_TYPE {
|
||||
Bounty
|
||||
Question
|
||||
Story
|
||||
}
|
||||
|
||||
type ParticipationInfo {
|
||||
createdAt: Date!
|
||||
email: String!
|
||||
hacking_status: TournamentMakerHackingStatusEnum!
|
||||
}
|
||||
|
||||
union Post = Bounty | Question | Story
|
||||
|
||||
interface PostBase {
|
||||
body: String!
|
||||
createdAt: Date!
|
||||
excerpt: String!
|
||||
id: Int!
|
||||
is_published: Boolean
|
||||
title: String!
|
||||
updatedAt: Date!
|
||||
votes_count: Int!
|
||||
}
|
||||
|
||||
type PostComment {
|
||||
author: Author!
|
||||
body: String!
|
||||
created_at: Date!
|
||||
id: Int!
|
||||
parentId: Int
|
||||
votes_count: Int!
|
||||
}
|
||||
|
||||
input ProfileDetailsInput {
|
||||
avatar: ImageInput
|
||||
bio: String
|
||||
discord: String
|
||||
email: String
|
||||
github: String
|
||||
jobTitle: String
|
||||
lightning_address: String
|
||||
linkedin: String
|
||||
location: String
|
||||
name: String
|
||||
twitter: String
|
||||
website: String
|
||||
}
|
||||
|
||||
input ProfileRolesInput {
|
||||
roles: [MakerRoleInput!]!
|
||||
skills: [MakerSkillInput!]!
|
||||
}
|
||||
|
||||
type Project {
|
||||
awards: [Award!]!
|
||||
capabilities: [Capability!]!
|
||||
category: Category!
|
||||
cover_image: String!
|
||||
description: String!
|
||||
discord: String
|
||||
github: String
|
||||
hashtag: String!
|
||||
id: Int!
|
||||
launch_status: ProjectLaunchStatusEnum!
|
||||
lightning_address: String
|
||||
lnurl_callback_url: String
|
||||
members: [ProjectMember!]!
|
||||
permissions: [ProjectPermissionEnum!]!
|
||||
recruit_roles: [MakerRole!]!
|
||||
screenshots: [String!]!
|
||||
slack: String
|
||||
stories: [Story!]!
|
||||
tagline: String!
|
||||
tags: [Tag!]!
|
||||
telegram: String
|
||||
thumbnail_image: String!
|
||||
title: String!
|
||||
tournaments: [Tournament!]!
|
||||
twitter: String
|
||||
votes_count: Int!
|
||||
website: String!
|
||||
}
|
||||
|
||||
enum ProjectLaunchStatusEnum {
|
||||
Launched
|
||||
WIP
|
||||
}
|
||||
|
||||
type ProjectMember {
|
||||
role: TEAM_MEMBER_ROLE!
|
||||
user: User!
|
||||
}
|
||||
|
||||
enum ProjectPermissionEnum {
|
||||
DeleteProject
|
||||
UpdateAdmins
|
||||
UpdateInfo
|
||||
UpdateMembers
|
||||
}
|
||||
|
||||
type Query {
|
||||
allCategories: [Category!]!
|
||||
allProjects(skip: Int = 0, take: Int = 50): [Project!]!
|
||||
checkValidProjectHashtag(hashtag: String!, projectId: Int): Boolean!
|
||||
getAllCapabilities: [Capability!]!
|
||||
getAllHackathons(sortBy: String, tag: Int): [Hackathon!]!
|
||||
getAllMakersRoles: [GenericMakerRole!]!
|
||||
getAllMakersSkills: [MakerSkill!]!
|
||||
getCategory(id: Int!): Category!
|
||||
getDonationsStats: DonationsStats!
|
||||
getFeed(skip: Int = 0, sortBy: String, tag: Int = 0, take: Int = 10): [Post!]!
|
||||
getLnurlDetailsForProject(project_id: Int!): LnurlDetails!
|
||||
getMakersInTournament(openToConnect: Boolean, roleId: Int, search: String, skip: Int = 0, take: Int = 10, tournamentId: Int!): TournamentMakersResponse!
|
||||
getMyDrafts(type: POST_TYPE!): [Post!]!
|
||||
getPostById(id: Int!, type: POST_TYPE!): Post!
|
||||
getProject(id: Int, tag: String): Project!
|
||||
getProjectsInTournament(roleId: Int, search: String, skip: Int = 0, take: Int = 10, tournamentId: Int!): TournamentProjectsResponse!
|
||||
getTournamentById(id: Int!): Tournament!
|
||||
getTournamentToRegister: [Tournament!]!
|
||||
getTrendingPosts: [Post!]!
|
||||
hottestProjects(skip: Int = 0, take: Int = 50): [Project!]!
|
||||
me: MyProfile
|
||||
newProjects(skip: Int = 0, take: Int = 50): [Project!]!
|
||||
officialTags: [Tag!]!
|
||||
popularTags: [Tag!]!
|
||||
profile(id: Int!): User
|
||||
projectsByCategory(category_id: Int!, skip: Int = 0, take: Int = 10): [Project!]!
|
||||
searchProjects(search: String!, skip: Int = 0, take: Int = 50): [Project!]!
|
||||
searchUsers(value: String!): [User!]!
|
||||
similarMakers(id: Int!): [User!]!
|
||||
similarProjects(id: Int!): [Project!]!
|
||||
tournamentParticipationInfo(tournamentId: Int!): ParticipationInfo
|
||||
}
|
||||
|
||||
type Question implements PostBase {
|
||||
author: Author!
|
||||
body: String!
|
||||
createdAt: Date!
|
||||
excerpt: String!
|
||||
id: Int!
|
||||
is_published: Boolean
|
||||
tags: [Tag!]!
|
||||
title: String!
|
||||
type: String!
|
||||
updatedAt: Date!
|
||||
votes_count: Int!
|
||||
}
|
||||
|
||||
input RegisterInTournamentInput {
|
||||
email: String!
|
||||
hacking_status: TournamentMakerHackingStatusEnum!
|
||||
}
|
||||
|
||||
enum RoleLevelEnum {
|
||||
Advanced
|
||||
Beginner
|
||||
Hobbyist
|
||||
Intermediate
|
||||
Pro
|
||||
}
|
||||
|
||||
type Story implements PostBase {
|
||||
author: Author!
|
||||
body: String!
|
||||
comments: [PostComment!]!
|
||||
comments_count: Int!
|
||||
cover_image: String
|
||||
createdAt: Date!
|
||||
excerpt: String!
|
||||
id: Int!
|
||||
is_published: Boolean
|
||||
project: Project
|
||||
tags: [Tag!]!
|
||||
title: String!
|
||||
type: String!
|
||||
updatedAt: Date!
|
||||
votes_count: Int!
|
||||
}
|
||||
|
||||
input StoryInputType {
|
||||
body: String!
|
||||
cover_image: ImageInput
|
||||
id: Int
|
||||
is_published: Boolean
|
||||
project_id: Int
|
||||
tags: [String!]!
|
||||
title: String!
|
||||
}
|
||||
|
||||
enum TEAM_MEMBER_ROLE {
|
||||
Admin
|
||||
Maker
|
||||
Owner
|
||||
}
|
||||
|
||||
type Tag {
|
||||
description: String
|
||||
icon: String
|
||||
id: Int!
|
||||
isOfficial: Boolean
|
||||
title: String!
|
||||
}
|
||||
|
||||
input TeamMemberInput {
|
||||
id: Int!
|
||||
role: TEAM_MEMBER_ROLE!
|
||||
}
|
||||
|
||||
type Tournament {
|
||||
cover_image: String!
|
||||
description: String!
|
||||
end_date: Date!
|
||||
events: [TournamentEvent!]!
|
||||
events_count: Int!
|
||||
faqs: [TournamentFAQ!]!
|
||||
id: Int!
|
||||
judges: [TournamentJudge!]!
|
||||
location: String!
|
||||
makers_count: Int!
|
||||
prizes: [TournamentPrize!]!
|
||||
projects_count: Int!
|
||||
start_date: Date!
|
||||
thumbnail_image: String!
|
||||
title: String!
|
||||
website: String!
|
||||
}
|
||||
|
||||
type TournamentEvent {
|
||||
description: String!
|
||||
ends_at: Date!
|
||||
id: Int!
|
||||
image: String!
|
||||
links: [String!]!
|
||||
location: String!
|
||||
starts_at: Date!
|
||||
title: String!
|
||||
type: TournamentEventTypeEnum!
|
||||
website: String!
|
||||
}
|
||||
|
||||
enum TournamentEventTypeEnum {
|
||||
IRLMeetup
|
||||
OnlineMeetup
|
||||
TwitterSpace
|
||||
Workshop
|
||||
}
|
||||
|
||||
type TournamentFAQ {
|
||||
answer: String!
|
||||
question: String!
|
||||
}
|
||||
|
||||
type TournamentJudge {
|
||||
avatar: String!
|
||||
company: String!
|
||||
name: String!
|
||||
}
|
||||
|
||||
enum TournamentMakerHackingStatusEnum {
|
||||
OpenToConnect
|
||||
Solo
|
||||
}
|
||||
|
||||
type TournamentMakersResponse {
|
||||
hasNext: Boolean
|
||||
hasPrev: Boolean
|
||||
makers: [TournamentParticipant!]!
|
||||
}
|
||||
|
||||
type TournamentParticipant {
|
||||
hacking_status: TournamentMakerHackingStatusEnum!
|
||||
is_registered: Boolean
|
||||
user: User!
|
||||
}
|
||||
|
||||
type TournamentPrize {
|
||||
amount: String!
|
||||
image: String!
|
||||
title: String!
|
||||
}
|
||||
|
||||
type TournamentProjectsResponse {
|
||||
hasNext: Boolean
|
||||
hasPrev: Boolean
|
||||
projects: [Project!]!
|
||||
}
|
||||
|
||||
input UpdateProjectInput {
|
||||
capabilities: [Int!]!
|
||||
category_id: Int!
|
||||
cover_image: ImageInput!
|
||||
description: String!
|
||||
discord: String
|
||||
github: String
|
||||
hashtag: String!
|
||||
id: Int
|
||||
launch_status: ProjectLaunchStatusEnum!
|
||||
lightning_address: String
|
||||
members: [TeamMemberInput!]!
|
||||
recruit_roles: [Int!]!
|
||||
screenshots: [ImageInput!]!
|
||||
slack: String
|
||||
tagline: String!
|
||||
telegram: String
|
||||
thumbnail_image: ImageInput!
|
||||
title: String!
|
||||
tournaments: [Int!]!
|
||||
twitter: String
|
||||
website: String!
|
||||
}
|
||||
|
||||
input UpdateTournamentRegistrationInput {
|
||||
email: String
|
||||
hacking_status: TournamentMakerHackingStatusEnum
|
||||
}
|
||||
|
||||
type User implements BaseUser {
|
||||
avatar: String!
|
||||
bio: String
|
||||
discord: String
|
||||
github: String
|
||||
id: Int!
|
||||
in_tournament(id: Int!): Boolean!
|
||||
jobTitle: String
|
||||
join_date: Date!
|
||||
lightning_address: String
|
||||
linkedin: String
|
||||
location: String
|
||||
name: String!
|
||||
projects: [Project!]!
|
||||
role: String
|
||||
roles: [MakerRole!]!
|
||||
similar_makers: [User!]!
|
||||
skills: [MakerSkill!]!
|
||||
stories: [Story!]!
|
||||
tournaments: [Tournament!]!
|
||||
twitter: String
|
||||
website: String
|
||||
}
|
||||
|
||||
input UserKeyInputType {
|
||||
key: String!
|
||||
name: String!
|
||||
}
|
||||
|
||||
enum VOTE_ITEM_TYPE {
|
||||
Bounty
|
||||
PostComment
|
||||
Project
|
||||
Question
|
||||
Story
|
||||
User
|
||||
}
|
||||
|
||||
type Vote {
|
||||
amount_in_sat: Int!
|
||||
id: Int!
|
||||
item_id: Int!
|
||||
item_type: VOTE_ITEM_TYPE!
|
||||
paid: Boolean!
|
||||
payment_hash: String!
|
||||
payment_request: String!
|
||||
}
|
||||
|
||||
type WalletKey {
|
||||
createdAt: Date!
|
||||
is_current: Boolean!
|
||||
key: String!
|
||||
name: String!
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
const { makeSchema } = require('nexus');
|
||||
const { join } = require('path');
|
||||
const types = require('../types')
|
||||
|
||||
|
||||
const schema = makeSchema({
|
||||
types: types,
|
||||
outputs: {
|
||||
typegen: join(__dirname, '..', 'nexus-typegen.ts'),
|
||||
schema: join(__dirname, '..', 'schema.graphql'),
|
||||
},
|
||||
})
|
||||
|
||||
module.exports = schema;
|
||||
|
||||
// const { gql } = require("apollo-server-lambda");
|
||||
// const projectSchema = require('./project')
|
||||
// const categorySchema = require('./category')
|
||||
// const voteSchema = require('./vote')
|
||||
|
||||
// const linkSchema = gql`
|
||||
// type Query {
|
||||
// _: Boolean
|
||||
// }
|
||||
|
||||
// type Mutation {
|
||||
// _: Boolean
|
||||
// }
|
||||
|
||||
// type Subscription {
|
||||
// _: Boolean
|
||||
// }
|
||||
// `;
|
||||
|
||||
// module.exports = [linkSchema, categorySchema, projectSchema, voteSchema];
|
||||
@@ -1,24 +0,0 @@
|
||||
const { Kind } = require("graphql")
|
||||
const { scalarType } = require("nexus")
|
||||
|
||||
const DateScalar = scalarType({
|
||||
name: 'Date',
|
||||
asNexusMethod: 'date',
|
||||
description: 'Date custom scalar type',
|
||||
parseValue(value) {
|
||||
return new Date(value)
|
||||
},
|
||||
serialize(value) {
|
||||
return value.toISOString()
|
||||
},
|
||||
parseLiteral(ast) {
|
||||
if (ast.kind === Kind.INT) {
|
||||
return new Date(ast.value)
|
||||
}
|
||||
return null
|
||||
},
|
||||
})
|
||||
|
||||
module.exports = {
|
||||
DateScalar
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
const {
|
||||
intArg,
|
||||
objectType,
|
||||
extendType,
|
||||
nonNull,
|
||||
} = require('nexus');
|
||||
const { prisma } = require('../../../prisma');
|
||||
const { resolveImgObjectToUrl } = require('../../../utils/resolveImageUrl');
|
||||
|
||||
|
||||
const Category = objectType({
|
||||
name: 'Category',
|
||||
definition(t) {
|
||||
t.nonNull.int('id');
|
||||
t.nonNull.string('title');
|
||||
t.string('cover_image', {
|
||||
async resolve(parent) {
|
||||
return prisma.category.findUnique({ where: { id: parent.id } }).cover_image_rel().then(resolveImgObjectToUrl)
|
||||
}
|
||||
});
|
||||
t.string('icon');
|
||||
|
||||
|
||||
t.nonNull.int('votes_sum', {
|
||||
async resolve(parent) {
|
||||
const projects = await prisma.category.findUnique({ where: { id: parent.id } }).project()
|
||||
return projects.reduce((total, project) => total + project.votes_count, 0);
|
||||
}
|
||||
});
|
||||
t.nonNull.int('apps_count', {
|
||||
async resolve(parent) {
|
||||
const projects = await prisma.category.findUnique({ where: { id: parent.id } }).project();
|
||||
return projects.length;
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
t.nonNull.list.nonNull.field('project', {
|
||||
type: "Project",
|
||||
resolve: (parent) => {
|
||||
return parent.project ?? prisma.category.findUnique({
|
||||
where: { id: parent.id }
|
||||
}).project()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const allCategoriesQuery = extendType({
|
||||
type: "Query",
|
||||
definition(t) {
|
||||
t.nonNull.list.nonNull.field('allCategories', {
|
||||
type: "Category",
|
||||
resolve: async () => {
|
||||
const categories = await prisma.category.findMany({
|
||||
include: {
|
||||
_count: {
|
||||
select: {
|
||||
project: true
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
categories.sort((c1, c2) => c2._count.project - c1._count.project)
|
||||
return categories;
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const getCategory = extendType({
|
||||
type: "Query",
|
||||
definition(t) {
|
||||
t.nonNull.field('getCategory', {
|
||||
type: "Category",
|
||||
args: {
|
||||
id: nonNull(intArg())
|
||||
},
|
||||
resolve(parent, { id }) {
|
||||
return prisma.category.findUnique({
|
||||
where: { id }
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
module.exports = {
|
||||
// Types
|
||||
Category,
|
||||
|
||||
// Queries
|
||||
allCategoriesQuery,
|
||||
getCategory
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
const { createHash } = require('crypto');
|
||||
const { parsePaymentRequest } = require('invoices');
|
||||
const {
|
||||
intArg,
|
||||
objectType,
|
||||
stringArg,
|
||||
extendType,
|
||||
nonNull,
|
||||
} = require('nexus');
|
||||
const { prisma } = require('../../../prisma');
|
||||
const { CONSTS } = require('../../../utils');
|
||||
const { getPaymetRequestForItem, hexToUint8Array } = require('./helpers');
|
||||
|
||||
|
||||
|
||||
const Donation = objectType({
|
||||
name: 'Donation',
|
||||
definition(t) {
|
||||
t.nonNull.int('id');
|
||||
t.nonNull.int('amount');
|
||||
t.nonNull.date('createdAt');
|
||||
t.nonNull.string('payment_request');
|
||||
t.nonNull.string('payment_hash');
|
||||
t.nonNull.boolean('paid');
|
||||
|
||||
t.field('by', {
|
||||
type: 'User',
|
||||
resolve: (parent) => {
|
||||
return prisma.donation.findUnique({ where: { id: parent.id } }).donor();
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
|
||||
const donateMutation = extendType({
|
||||
type: "Mutation",
|
||||
definition(t) {
|
||||
t.nonNull.field('donate', {
|
||||
type: "Donation",
|
||||
args: {
|
||||
amount_in_sat: nonNull(intArg())
|
||||
},
|
||||
resolve: async (_, args) => {
|
||||
|
||||
const { amount_in_sat } = args;
|
||||
const lightning_address = CONSTS.BOLT_FUN_LIGHTNING_ADDRESS;
|
||||
const pr = await getPaymetRequestForItem(lightning_address, args.amount_in_sat);
|
||||
const invoice = parsePaymentRequest({ request: pr });
|
||||
|
||||
return prisma.donation.create({
|
||||
data: {
|
||||
amount: amount_in_sat,
|
||||
payment_request: pr,
|
||||
payment_hash: invoice.id,
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const confirmDonateMutation = extendType({
|
||||
type: "Mutation",
|
||||
definition(t) {
|
||||
t.nonNull.field('confirmDonation', {
|
||||
type: "Donation",
|
||||
args: {
|
||||
payment_request: nonNull(stringArg()),
|
||||
preimage: nonNull(stringArg())
|
||||
},
|
||||
resolve: async (_, args) => {
|
||||
const paymentHash = createHash("sha256")
|
||||
.update(hexToUint8Array(args.preimage))
|
||||
.digest("hex");
|
||||
// look for a vote for the payment request and the calculated payment hash
|
||||
const donation = await prisma.donation.findFirst({
|
||||
where: {
|
||||
payment_request: args.payment_request,
|
||||
payment_hash: paymentHash,
|
||||
},
|
||||
});
|
||||
|
||||
// if we find a donation it means the preimage is correct and we update the donation and mark it as paid
|
||||
// can we write this nicer?
|
||||
if (donation) {
|
||||
|
||||
// return the current donation
|
||||
return prisma.donation.update({
|
||||
where: { id: donation.id },
|
||||
data: {
|
||||
paid: true,
|
||||
preimage: args.preimage,
|
||||
}
|
||||
});
|
||||
} else {
|
||||
throw new Error("Invalid preimage");
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const DonationsStats = objectType({
|
||||
name: 'DonationsStats',
|
||||
definition(t) {
|
||||
t.nonNull.string("prizes");
|
||||
t.nonNull.string("touranments");
|
||||
t.nonNull.string("donations");
|
||||
t.nonNull.string("applications");
|
||||
},
|
||||
})
|
||||
|
||||
const getDonationsStats = extendType({
|
||||
type: "Query",
|
||||
definition(t) {
|
||||
t.nonNull.field('getDonationsStats', {
|
||||
type: "DonationsStats",
|
||||
async resolve() {
|
||||
const [donations, applications] = await Promise.all([
|
||||
prisma.donation.aggregate({
|
||||
_sum: {
|
||||
amount: true
|
||||
},
|
||||
where: {
|
||||
paid: true
|
||||
}
|
||||
}).then(d => d._sum.amount ?? 0),
|
||||
prisma.project.count()]);
|
||||
// #TODO add a measurement unit for prizes & donations (eg. $ or sats or BTC)
|
||||
return {
|
||||
prizes: '$5.2k',
|
||||
touranments: 2,
|
||||
donations,
|
||||
applications
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
|
||||
module.exports = {
|
||||
// Types
|
||||
Donation,
|
||||
DonationsStats,
|
||||
// Queries
|
||||
donateMutation,
|
||||
confirmDonateMutation,
|
||||
getDonationsStats,
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
const {
|
||||
intArg,
|
||||
objectType,
|
||||
stringArg,
|
||||
extendType,
|
||||
nonNull,
|
||||
} = require('nexus');
|
||||
const { prisma } = require('../../../prisma');
|
||||
const { resolveImgObjectToUrl } = require('../../../utils/resolveImageUrl');
|
||||
|
||||
|
||||
|
||||
const Hackathon = objectType({
|
||||
name: 'Hackathon',
|
||||
definition(t) {
|
||||
t.nonNull.int('id');
|
||||
t.nonNull.string('title');
|
||||
t.nonNull.string('description');
|
||||
t.nonNull.string('cover_image', {
|
||||
async resolve(parent) {
|
||||
return prisma.hackathon.findUnique({ where: { id: parent.id } }).cover_image_rel().then(resolveImgObjectToUrl)
|
||||
}
|
||||
});
|
||||
t.nonNull.date('start_date');
|
||||
t.nonNull.date('end_date');
|
||||
t.nonNull.string('location');
|
||||
t.nonNull.string('website');
|
||||
t.nonNull.list.nonNull.field('tags', {
|
||||
type: "Tag",
|
||||
resolve: (parent) => {
|
||||
return prisma.hackathon.findUnique({ where: { id: parent.id } }).tags();
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
const getAllHackathons = extendType({
|
||||
type: "Query",
|
||||
definition(t) {
|
||||
t.nonNull.list.nonNull.field('getAllHackathons', {
|
||||
type: "Hackathon",
|
||||
args: {
|
||||
sortBy: stringArg(),
|
||||
tag: intArg(),
|
||||
},
|
||||
resolve(_, args) {
|
||||
const { sortBy, tag } = args;
|
||||
return prisma.hackathon.findMany({
|
||||
where: {
|
||||
...(sortBy === 'Upcoming' && {
|
||||
start_date: {
|
||||
gte: new Date(),
|
||||
}
|
||||
}),
|
||||
...(sortBy === 'Live' && {
|
||||
start_date: { lte: new Date() },
|
||||
end_date: { gte: new Date() }
|
||||
}),
|
||||
...(sortBy === 'Finished' && {
|
||||
end_date: {
|
||||
lt: new Date()
|
||||
}
|
||||
}),
|
||||
|
||||
|
||||
|
||||
...(tag && {
|
||||
tags: {
|
||||
some: {
|
||||
id: tag
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
orderBy: {
|
||||
start_date: "desc"
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
module.exports = {
|
||||
// Types
|
||||
Hackathon,
|
||||
// Queries
|
||||
getAllHackathons,
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
const { intArg } = require("nexus");
|
||||
const axios = require("axios");
|
||||
|
||||
|
||||
function hexToUint8Array(hexString) {
|
||||
const match = hexString.match(/.{1,2}/g);
|
||||
if (match) {
|
||||
return new Uint8Array(match.map((byte) => parseInt(byte, 16)));
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: generaly validate LNURL responses
|
||||
// get lnurl params
|
||||
function getLnurlDetails(lnurl) {
|
||||
return axios.get(lnurl);
|
||||
}
|
||||
|
||||
// parse lightning address and return a url that can be
|
||||
// used in a request
|
||||
function lightningAddressToLnurl(lightning_address) {
|
||||
const [name, domain] = lightning_address.split("@");
|
||||
return `https://${domain}/.well-known/lnurlp/${name}`;
|
||||
}
|
||||
|
||||
// when pressing tip or selecting an amount.
|
||||
// this is used for caching so the frontend doesnt
|
||||
// have to make an additional http request to get
|
||||
// the callback url for future visits
|
||||
async function getLnurlCallbackUrl(lightning_address) {
|
||||
return getLnurlDetails(lightningAddressToLnurl(lightning_address)).then(
|
||||
(response) => {
|
||||
return response.data.callback;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
async function getPaymetRequestForItem(lightning_address, amount_in_sat) {
|
||||
// # NOTE: CACHING LNURL CALLBACK URLS + PARAMETERS
|
||||
// LNURL flows have a lot of back and forth and can impact
|
||||
// the load time for your application users.
|
||||
// You may consider caching the callback url, or resolved
|
||||
// parameters but be mindful of this.
|
||||
// The LNURL service provider can change the callback url
|
||||
// details or the paramters that is returned we must be
|
||||
// careful when trying to optimise the amount of
|
||||
// requests so be mindful of this when you are storing
|
||||
// these items.
|
||||
|
||||
const amount = amount_in_sat * 1000; // msats
|
||||
let lnurlCallbackUrl = await getLnurlCallbackUrl(lightning_address);
|
||||
return axios
|
||||
.get(lnurlCallbackUrl, { params: { amount } })
|
||||
.then((prResponse) => {
|
||||
return prResponse.data.pr;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
const paginationArgs = (args) => {
|
||||
const { take = 10, skip = 0 } = args ?? {}
|
||||
return {
|
||||
take: intArg({ default: take }),
|
||||
skip: intArg({ default: skip })
|
||||
}
|
||||
}
|
||||
|
||||
const removeNulls = (obj) => {
|
||||
let res = {};
|
||||
for (const key in obj) {
|
||||
if (obj[key] != null) {
|
||||
res[key] = obj[key];
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getPaymetRequestForItem,
|
||||
hexToUint8Array,
|
||||
lightningAddressToLnurl,
|
||||
getLnurlDetails,
|
||||
paginationArgs,
|
||||
removeNulls
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
const scalars = require('./_scalars')
|
||||
const misc = require('./misc')
|
||||
const category = require('./category')
|
||||
const project = require('./project')
|
||||
const vote = require('./vote')
|
||||
const post = require('./post')
|
||||
const users = require('./users')
|
||||
const hackathon = require('./hackathon')
|
||||
const tournament = require('./tournament')
|
||||
const donation = require('./donation')
|
||||
const tag = require('./tag')
|
||||
|
||||
module.exports = {
|
||||
...misc,
|
||||
...tag,
|
||||
...scalars,
|
||||
...category,
|
||||
...project,
|
||||
...vote,
|
||||
...post,
|
||||
...users,
|
||||
...hackathon,
|
||||
...tournament,
|
||||
...donation,
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
const { objectType, extendType, inputObjectType } = require("nexus");
|
||||
const { prisma } = require('../../../prisma');
|
||||
|
||||
const ImageInput = inputObjectType({
|
||||
name: 'ImageInput',
|
||||
definition(t) {
|
||||
t.string('id');
|
||||
t.string('name');
|
||||
t.nonNull.string('url');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
module.exports = {
|
||||
// Types
|
||||
ImageInput,
|
||||
|
||||
// Queries
|
||||
}
|
||||
@@ -1,698 +0,0 @@
|
||||
const {
|
||||
intArg,
|
||||
objectType,
|
||||
extendType,
|
||||
nonNull,
|
||||
interfaceType,
|
||||
unionType,
|
||||
stringArg,
|
||||
enumType,
|
||||
arg,
|
||||
inputObjectType,
|
||||
} = require('nexus');
|
||||
const { paginationArgs } = require('./helpers');
|
||||
const { prisma } = require('../../../prisma');
|
||||
const { getUserByPubKey } = require('../../../auth/utils/helperFuncs');
|
||||
const { ApolloError } = require('apollo-server-lambda');
|
||||
const { marked } = require('marked');
|
||||
const { resolveImgObjectToUrl } = require('../../../utils/resolveImageUrl');
|
||||
const { ImageInput } = require('./misc');
|
||||
const { deleteImage } = require('../../../services/imageUpload.service');
|
||||
const { logError } = require('../../../utils/logger');
|
||||
|
||||
|
||||
const POST_TYPE = enumType({
|
||||
name: 'POST_TYPE',
|
||||
members: ['Story', 'Bounty', 'Question'],
|
||||
});
|
||||
|
||||
const asType = type => (obj) => {
|
||||
if (Array.isArray(obj)) return obj.map(o => ({ ...o, type }))
|
||||
return { ...obj, type }
|
||||
}
|
||||
|
||||
const asStoryType = asType('Story')
|
||||
const asQuestionType = asType('Question')
|
||||
const asBountyType = asType('Bounty')
|
||||
|
||||
|
||||
const Author = objectType({
|
||||
name: 'Author',
|
||||
definition(t) {
|
||||
t.nonNull.int('id');
|
||||
t.nonNull.string('name');
|
||||
t.nonNull.string('avatar', {
|
||||
async resolve(parent) {
|
||||
return prisma.user.findUnique({ where: { id: parent.id } }).avatar_rel().then(resolveImgObjectToUrl)
|
||||
}
|
||||
});
|
||||
t.nonNull.date('join_date');
|
||||
|
||||
t.string('lightning_address');
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
const PostBase = interfaceType({
|
||||
name: 'PostBase',
|
||||
resolveType(item) {
|
||||
return item.type
|
||||
},
|
||||
definition(t) {
|
||||
t.nonNull.int('id');
|
||||
t.nonNull.string('title');
|
||||
t.nonNull.date('createdAt');
|
||||
t.nonNull.date('updatedAt');
|
||||
t.nonNull.string('body');
|
||||
t.nonNull.string('excerpt');
|
||||
t.nonNull.int('votes_count');
|
||||
t.boolean('is_published');
|
||||
},
|
||||
})
|
||||
|
||||
const Story = objectType({
|
||||
name: 'Story',
|
||||
definition(t) {
|
||||
t.implements('PostBase');
|
||||
t.nonNull.string('type', {
|
||||
resolve: () => t.typeName
|
||||
});
|
||||
t.string('cover_image', {
|
||||
async resolve(parent) {
|
||||
return prisma.story.findUnique({ where: { id: parent.id } }).cover_image_rel().then(resolveImgObjectToUrl)
|
||||
}
|
||||
});
|
||||
t.nonNull.list.nonNull.field('comments', {
|
||||
type: "PostComment",
|
||||
resolve: (parent) => []
|
||||
});
|
||||
t.nonNull.list.nonNull.field('tags', {
|
||||
type: "Tag",
|
||||
resolve: (parent) => prisma.story.findUnique({ where: { id: parent.id } }).tags()
|
||||
});
|
||||
t.nonNull.int('comments_count', {
|
||||
resolve: async (parent) => {
|
||||
const post = await prisma.story.findUnique({
|
||||
where: { id: parent.id },
|
||||
include: {
|
||||
_count: {
|
||||
select: {
|
||||
comments: true
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
return post._count.comments;
|
||||
}
|
||||
});
|
||||
t.nonNull.field('author', {
|
||||
type: "Author",
|
||||
resolve: (parent) =>
|
||||
prisma.story.findUnique({ where: { id: parent.id } }).user()
|
||||
|
||||
});
|
||||
|
||||
t.field('project', {
|
||||
type: "Project",
|
||||
resolve(parent) {
|
||||
return prisma.story.findUnique({ where: { id: parent.id } }).project();
|
||||
}
|
||||
})
|
||||
|
||||
},
|
||||
})
|
||||
|
||||
const StoryInputType = inputObjectType({
|
||||
name: 'StoryInputType',
|
||||
definition(t) {
|
||||
t.int('id');
|
||||
t.nonNull.string('title');
|
||||
t.nonNull.string('body');
|
||||
t.field('cover_image', {
|
||||
type: ImageInput
|
||||
})
|
||||
t.nonNull.list.nonNull.string('tags');
|
||||
t.boolean('is_published')
|
||||
t.int('project_id')
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const BountyApplication = objectType({
|
||||
name: 'BountyApplication',
|
||||
definition(t) {
|
||||
t.nonNull.int('id');
|
||||
t.nonNull.string('date');
|
||||
t.nonNull.string('workplan');
|
||||
t.nonNull.field('author', {
|
||||
type: "Author"
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
const Bounty = objectType({
|
||||
name: 'Bounty',
|
||||
definition(t) {
|
||||
t.implements('PostBase');
|
||||
t.nonNull.string('type', {
|
||||
resolve: () => 'Bounty'
|
||||
});
|
||||
t.string('cover_image');
|
||||
t.nonNull.string('deadline');
|
||||
t.nonNull.int('reward_amount');
|
||||
t.nonNull.int('applicants_count');
|
||||
t.nonNull.list.nonNull.field('applications', {
|
||||
type: "BountyApplication"
|
||||
});
|
||||
t.nonNull.field('author', {
|
||||
type: "Author",
|
||||
resolve: (parent) => {
|
||||
return prisma.bounty.findUnique({ where: { id: parent.id } }).user();
|
||||
}
|
||||
});
|
||||
|
||||
t.nonNull.list.nonNull.field('tags', {
|
||||
type: "Tag",
|
||||
resolve: (parent) => []
|
||||
});
|
||||
},
|
||||
})
|
||||
|
||||
const Question = objectType({
|
||||
name: 'Question',
|
||||
definition(t) {
|
||||
t.implements('PostBase');
|
||||
t.nonNull.string('type', {
|
||||
resolve: () => 'Question',
|
||||
|
||||
});
|
||||
|
||||
t.nonNull.list.nonNull.field('tags', {
|
||||
type: "Tag",
|
||||
resolve: (parent) => prisma.question.findUnique({ where: { id: parent.id } }).tags()
|
||||
});
|
||||
|
||||
// t.nonNull.int('answers_count');
|
||||
// t.nonNull.list.nonNull.field('comments', {
|
||||
// type: "PostComment",
|
||||
// resolve: (parent) => {
|
||||
// return prisma.question.findUnique({ where: { id: parent.id } }).comments();
|
||||
// }
|
||||
// });
|
||||
|
||||
t.nonNull.field('author', {
|
||||
type: "Author",
|
||||
resolve: (parent) => {
|
||||
return prisma.question.findUnique({ where: { id: parent.id } }).user();
|
||||
}
|
||||
});
|
||||
},
|
||||
})
|
||||
|
||||
const PostComment = objectType({
|
||||
name: 'PostComment',
|
||||
definition(t) {
|
||||
t.nonNull.int('id');
|
||||
t.nonNull.date('created_at');
|
||||
t.nonNull.string('body');
|
||||
t.nonNull.field('author', {
|
||||
type: "Author"
|
||||
});
|
||||
t.int('parentId');
|
||||
t.nonNull.int('votes_count');
|
||||
}
|
||||
})
|
||||
|
||||
const Post = unionType({
|
||||
name: 'Post',
|
||||
definition(t) {
|
||||
t.members('Story', 'Bounty', 'Question')
|
||||
},
|
||||
resolveType: (item) => {
|
||||
return item.type
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
const getFeed = extendType({
|
||||
type: "Query",
|
||||
definition(t) {
|
||||
t.nonNull.list.nonNull.field('getFeed', {
|
||||
type: "Post",
|
||||
args: {
|
||||
...paginationArgs({ take: 10 }),
|
||||
sortBy: stringArg(), // all, popular, trending, newest
|
||||
tag: intArg({
|
||||
default: 0
|
||||
})
|
||||
},
|
||||
resolve(_, { take, skip, tag, sortBy, }) {
|
||||
|
||||
|
||||
let orderBy = { createdAt: "desc" };
|
||||
|
||||
if (sortBy === 'popular')
|
||||
orderBy = { votes_count: 'desc' };
|
||||
else if (sortBy === 'newest')
|
||||
orderBy = { createdAt: "desc" };
|
||||
|
||||
return prisma.story.findMany({
|
||||
orderBy: orderBy,
|
||||
where: {
|
||||
...(tag && {
|
||||
tags: {
|
||||
some: {
|
||||
id: tag
|
||||
}
|
||||
},
|
||||
}),
|
||||
is_published: true,
|
||||
},
|
||||
skip,
|
||||
take,
|
||||
}).then(asStoryType)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const getTrendingPosts = extendType({
|
||||
type: "Query",
|
||||
definition(t) {
|
||||
t.nonNull.list.nonNull.field('getTrendingPosts', {
|
||||
type: "Post",
|
||||
args: {
|
||||
},
|
||||
resolve() {
|
||||
const now = new Date();
|
||||
const lastWeekDate = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 7)
|
||||
return prisma.story.findMany({
|
||||
where: {
|
||||
createdAt: {
|
||||
gte: lastWeekDate
|
||||
},
|
||||
is_published: true,
|
||||
},
|
||||
orderBy: { votes_count: 'desc' },
|
||||
take: 5,
|
||||
}).then(asStoryType)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
|
||||
const getMyDrafts = extendType({
|
||||
type: "Query",
|
||||
definition(t) {
|
||||
t.nonNull.list.nonNull.field('getMyDrafts', {
|
||||
type: "Post",
|
||||
args: {
|
||||
type: arg({
|
||||
type: nonNull('POST_TYPE')
|
||||
})
|
||||
},
|
||||
async resolve(parent, { type }, ctx) {
|
||||
const user = await getUserByPubKey(ctx.userPubKey);
|
||||
// Do some validation
|
||||
if (!user)
|
||||
throw new ApolloError("Not Authenticated");
|
||||
|
||||
if (type === 'Story')
|
||||
return prisma.story.findMany({
|
||||
where: {
|
||||
is_published: false,
|
||||
user_id: user.id
|
||||
},
|
||||
orderBy: { createdAt: 'desc' },
|
||||
}).then(asStoryType)
|
||||
return []
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const getPostById = extendType({
|
||||
type: "Query",
|
||||
definition(t) {
|
||||
t.nonNull.field('getPostById', {
|
||||
type: "Post",
|
||||
args: {
|
||||
id: nonNull(intArg()),
|
||||
type: arg({
|
||||
type: nonNull('POST_TYPE')
|
||||
})
|
||||
},
|
||||
resolve(_, { id, type }) {
|
||||
if (type === 'Story')
|
||||
return prisma.story.findUnique({
|
||||
where: { id }
|
||||
}).then(asStoryType)
|
||||
|
||||
if (type === 'Question')
|
||||
return prisma.question.findUnique({
|
||||
where: { id }
|
||||
}).then(asQuestionType)
|
||||
return null
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const addCoverImage = async (providerImageId) => {
|
||||
const newCoverImage = await prisma.hostedImage.findFirst({
|
||||
where: {
|
||||
provider_image_id: providerImageId
|
||||
}
|
||||
})
|
||||
|
||||
if (!newCoverImage) throw new ApolloError("New cover image not found")
|
||||
|
||||
await prisma.hostedImage.update({
|
||||
where: {
|
||||
id: newCoverImage.id
|
||||
},
|
||||
data: {
|
||||
is_used: true
|
||||
}
|
||||
})
|
||||
|
||||
return newCoverImage
|
||||
}
|
||||
|
||||
const getHostedImageIdsFromBody = async (body, oldBodyImagesIds = null) => {
|
||||
let bodyImageIds = []
|
||||
|
||||
const regex = /(?:!\[(.*?)\]\((.*?)\))/g
|
||||
let match;
|
||||
while ((match = regex.exec(body))) {
|
||||
const [, , value] = match
|
||||
|
||||
// Useful for old external images in case of duplicates. We need to be sure we are targeting an image from the good story.
|
||||
const where = oldBodyImagesIds ? {
|
||||
AND: [
|
||||
{ url: value },
|
||||
{ id: { in: oldBodyImagesIds } }
|
||||
]
|
||||
} :
|
||||
{
|
||||
url: value,
|
||||
}
|
||||
|
||||
const hostedImage = await prisma.hostedImage.findFirst({
|
||||
where
|
||||
})
|
||||
if (hostedImage) {
|
||||
bodyImageIds.push(hostedImage.id)
|
||||
await prisma.hostedImage.update({
|
||||
where: {
|
||||
id: hostedImage.id
|
||||
},
|
||||
data: {
|
||||
is_used: true
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
return bodyImageIds
|
||||
}
|
||||
|
||||
const createStory = extendType({
|
||||
type: 'Mutation',
|
||||
definition(t) {
|
||||
t.field('createStory', {
|
||||
type: 'Story',
|
||||
args: { data: StoryInputType },
|
||||
async resolve(_root, args, ctx) {
|
||||
const { id, title, body, project_id, cover_image, tags, is_published } = args.data;
|
||||
const user = await getUserByPubKey(ctx.userPubKey);
|
||||
|
||||
// Do some validation
|
||||
if (!user)
|
||||
throw new ApolloError("Not Authenticated");
|
||||
|
||||
let was_published = false;
|
||||
|
||||
|
||||
// TODO: validate post data
|
||||
|
||||
let coverImage = null
|
||||
let bodyImageIds = []
|
||||
|
||||
|
||||
try {
|
||||
if (id) {
|
||||
const oldPost = await prisma.story.findFirst({
|
||||
where: { id },
|
||||
select: {
|
||||
user_id: true,
|
||||
is_published: true,
|
||||
cover_image_id: true,
|
||||
body_image_ids: true
|
||||
}
|
||||
})
|
||||
was_published = oldPost.is_published;
|
||||
if (user.id !== oldPost.user_id) throw new ApolloError("Not post author")
|
||||
|
||||
// Body images
|
||||
bodyImageIds = await getHostedImageIdsFromBody(body, oldPost.body_image_ids)
|
||||
|
||||
// Old cover image is found
|
||||
if (oldPost.cover_image_id) {
|
||||
const oldCoverImage = await prisma.hostedImage.findFirst({
|
||||
where: {
|
||||
id: oldPost.cover_image_id
|
||||
}
|
||||
})
|
||||
|
||||
// New cover image
|
||||
if (cover_image?.id && cover_image.id !== oldCoverImage?.provider_image_id) {
|
||||
await deleteImage(oldCoverImage.id)
|
||||
coverImage = await addCoverImage(cover_image.id)
|
||||
} else {
|
||||
coverImage = oldCoverImage
|
||||
}
|
||||
} else {
|
||||
// No old image found and new cover image
|
||||
if (cover_image?.id) {
|
||||
coverImage = await addCoverImage(cover_image.id)
|
||||
}
|
||||
}
|
||||
|
||||
// Remove unused body images
|
||||
const unusedImagesIds = oldPost.body_image_ids.filter(x => !bodyImageIds.includes(x));
|
||||
unusedImagesIds.map(async i => await deleteImage(i))
|
||||
|
||||
} else {
|
||||
// Body images
|
||||
bodyImageIds = await getHostedImageIdsFromBody(body)
|
||||
|
||||
// New story and new cover image
|
||||
if (cover_image?.id) {
|
||||
coverImage = await addCoverImage(cover_image.id)
|
||||
}
|
||||
}
|
||||
|
||||
// Preprocess & insert
|
||||
const htmlBody = marked.parse(body);
|
||||
const excerpt = htmlBody
|
||||
.replace(/<[^>]+>/g, '')
|
||||
.slice(0, 120)
|
||||
.replace(/&/g, "&")
|
||||
.replace(/'/g, "'")
|
||||
.replace(/"/g, '"')
|
||||
;
|
||||
|
||||
|
||||
const coverImageRel = coverImage ? {
|
||||
cover_image_rel: {
|
||||
connect:
|
||||
{
|
||||
id: coverImage ? coverImage.id : null
|
||||
}
|
||||
}
|
||||
} : {}
|
||||
|
||||
if (id) {
|
||||
await prisma.story.update({
|
||||
where: { id },
|
||||
data: {
|
||||
tags: {
|
||||
set: []
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
return prisma.story.update({
|
||||
where: { id },
|
||||
data: {
|
||||
title,
|
||||
body,
|
||||
cover_image: '',
|
||||
excerpt,
|
||||
is_published: was_published || is_published,
|
||||
project: project_id ? {
|
||||
connect: {
|
||||
id: project_id,
|
||||
},
|
||||
} : {
|
||||
disconnect: true
|
||||
},
|
||||
tags: {
|
||||
connectOrCreate:
|
||||
tags.map(tag => {
|
||||
tag = tag.toLowerCase().trim();
|
||||
return {
|
||||
where: {
|
||||
title: tag,
|
||||
},
|
||||
create: {
|
||||
title: tag
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
body_image_ids: bodyImageIds,
|
||||
...coverImageRel
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
logError(error)
|
||||
throw new ApolloError("Unexpected error happened...")
|
||||
})
|
||||
}
|
||||
|
||||
return prisma.story.create({
|
||||
data: {
|
||||
title,
|
||||
body,
|
||||
cover_image: '',
|
||||
excerpt,
|
||||
is_published,
|
||||
tags: {
|
||||
connectOrCreate:
|
||||
tags.map(tag => {
|
||||
tag = tag.toLowerCase().trim();
|
||||
return {
|
||||
where: {
|
||||
title: tag,
|
||||
},
|
||||
create: {
|
||||
title: tag
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
project: {
|
||||
connect: {
|
||||
id: project_id,
|
||||
}
|
||||
},
|
||||
user: {
|
||||
connect: {
|
||||
id: user.id,
|
||||
}
|
||||
},
|
||||
body_image_ids: bodyImageIds,
|
||||
...coverImageRel
|
||||
}
|
||||
}).catch(error => {
|
||||
logError(error)
|
||||
throw new ApolloError("Unexpected error happened...")
|
||||
})
|
||||
|
||||
|
||||
} catch (error) {
|
||||
logError(error)
|
||||
throw new ApolloError("Unexpected error happened...")
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
const deleteStory = extendType({
|
||||
type: 'Mutation',
|
||||
definition(t) {
|
||||
t.field('deleteStory', {
|
||||
type: 'Story',
|
||||
args: { id: nonNull(intArg()) },
|
||||
async resolve(_root, args, ctx) {
|
||||
const { id } = args;
|
||||
const user = await getUserByPubKey(ctx.userPubKey);
|
||||
// Do some validation
|
||||
if (!user)
|
||||
throw new ApolloError("Not Authenticated");
|
||||
|
||||
|
||||
const oldPost = await prisma.story.findFirst({
|
||||
where: { id },
|
||||
select: {
|
||||
user_id: true,
|
||||
body_image_ids: true,
|
||||
cover_image_id: true
|
||||
}
|
||||
})
|
||||
if (user.id !== oldPost.user_id)
|
||||
throw new ApolloError("Not post author")
|
||||
|
||||
const deletedPost = await prisma.story.delete({
|
||||
where: {
|
||||
id
|
||||
}
|
||||
})
|
||||
|
||||
const coverImage = await prisma.hostedImage.findMany({
|
||||
where: {
|
||||
OR: [
|
||||
{ id: oldPost.cover_image_id },
|
||||
{
|
||||
id: {
|
||||
in: oldPost.body_image_ids
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
provider_image_id: true
|
||||
}
|
||||
})
|
||||
coverImage.map(async i => await deleteImage(i.id))
|
||||
|
||||
return deletedPost
|
||||
}
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
module.exports = {
|
||||
// Types
|
||||
POST_TYPE,
|
||||
Author,
|
||||
PostBase,
|
||||
BountyApplication,
|
||||
Bounty,
|
||||
Story,
|
||||
StoryInputType,
|
||||
Question,
|
||||
PostComment,
|
||||
Post,
|
||||
// Queries
|
||||
getFeed,
|
||||
getPostById,
|
||||
getTrendingPosts,
|
||||
getMyDrafts,
|
||||
|
||||
// Mutations
|
||||
createStory,
|
||||
deleteStory,
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,63 +0,0 @@
|
||||
const { objectType, extendType } = require("nexus");
|
||||
const { prisma } = require('../../../prisma');
|
||||
|
||||
const Tag = objectType({
|
||||
name: 'Tag',
|
||||
definition(t) {
|
||||
t.nonNull.int('id');
|
||||
t.nonNull.string('title');
|
||||
t.string('icon');
|
||||
t.string('description');
|
||||
t.boolean('isOfficial');
|
||||
}
|
||||
});
|
||||
|
||||
const officialTags = extendType({
|
||||
type: "Query",
|
||||
definition(t) {
|
||||
t.nonNull.list.nonNull.field('officialTags', {
|
||||
type: "Tag",
|
||||
resolve: () => {
|
||||
return prisma.tag.findMany({
|
||||
orderBy: {
|
||||
title: 'asc'
|
||||
},
|
||||
where: {
|
||||
isOfficial: true
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const popularTags = extendType({
|
||||
type: "Query",
|
||||
definition(t) {
|
||||
t.nonNull.list.nonNull.field('popularTags', {
|
||||
type: "Tag",
|
||||
resolve: () => {
|
||||
return prisma.tag.findMany({
|
||||
where: {
|
||||
isOfficial: true
|
||||
},
|
||||
take: 8,
|
||||
orderBy: {
|
||||
stories: {
|
||||
_count: 'desc'
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
module.exports = {
|
||||
// Types
|
||||
Tag,
|
||||
|
||||
// Queries
|
||||
popularTags,
|
||||
officialTags,
|
||||
}
|
||||
@@ -1,566 +0,0 @@
|
||||
const {
|
||||
intArg,
|
||||
objectType,
|
||||
stringArg,
|
||||
extendType,
|
||||
nonNull,
|
||||
enumType,
|
||||
inputObjectType,
|
||||
booleanArg,
|
||||
} = require('nexus');
|
||||
const { getUserByPubKey } = require('../../../auth/utils/helperFuncs');
|
||||
const { resolveImgObjectToUrl } = require('../../../utils/resolveImageUrl');
|
||||
const { prisma } = require('../../../prisma');
|
||||
const { paginationArgs, removeNulls } = require('./helpers');
|
||||
|
||||
|
||||
|
||||
const TournamentPrize = objectType({
|
||||
name: 'TournamentPrize',
|
||||
definition(t) {
|
||||
t.nonNull.string('title');
|
||||
t.nonNull.string('amount');
|
||||
t.nonNull.string('image', {
|
||||
async resolve(parent) {
|
||||
return prisma.tournamentPrize.findUnique({ where: { id: parent.id } }).image_rel().then(resolveImgObjectToUrl)
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
const TournamentJudge = objectType({
|
||||
name: 'TournamentJudge',
|
||||
definition(t) {
|
||||
t.nonNull.string('name');
|
||||
t.nonNull.string('company');
|
||||
t.nonNull.string('avatar', {
|
||||
async resolve(parent) {
|
||||
return prisma.tournamentJudge.findUnique({ where: { id: parent.id } }).avatar_rel().then(resolveImgObjectToUrl)
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
const TournamentFAQ = objectType({
|
||||
name: 'TournamentFAQ',
|
||||
definition(t) {
|
||||
t.nonNull.string('question');
|
||||
t.nonNull.string('answer');
|
||||
}
|
||||
})
|
||||
|
||||
const TournamentParticipant = objectType({
|
||||
name: "TournamentParticipant",
|
||||
definition(t) {
|
||||
t.nonNull.field('hacking_status', { type: TournamentMakerHackingStatusEnum });
|
||||
t.boolean('is_registered')
|
||||
t.nonNull.field('user', { type: "User" })
|
||||
}
|
||||
})
|
||||
|
||||
const TournamentEventTypeEnum = enumType({
|
||||
name: 'TournamentEventTypeEnum',
|
||||
members: {
|
||||
TwitterSpace: 0,
|
||||
Workshop: 1,
|
||||
IRLMeetup: 2,
|
||||
OnlineMeetup: 3,
|
||||
},
|
||||
});
|
||||
|
||||
const TournamentMakerHackingStatusEnum = enumType({
|
||||
name: 'TournamentMakerHackingStatusEnum',
|
||||
members: {
|
||||
Solo: 0,
|
||||
OpenToConnect: 1,
|
||||
},
|
||||
});
|
||||
|
||||
const TournamentEvent = objectType({
|
||||
name: 'TournamentEvent',
|
||||
definition(t) {
|
||||
t.nonNull.int('id');
|
||||
t.nonNull.string('title');
|
||||
t.nonNull.string('image', {
|
||||
async resolve(parent) {
|
||||
return prisma.tournamentEvent.findUnique({ where: { id: parent.id } }).image_rel().then(resolveImgObjectToUrl)
|
||||
}
|
||||
});
|
||||
t.nonNull.string('description');
|
||||
t.nonNull.date('starts_at');
|
||||
t.nonNull.date('ends_at');
|
||||
t.nonNull.string('location');
|
||||
t.nonNull.string('website');
|
||||
t.nonNull.field('type', { type: TournamentEventTypeEnum })
|
||||
t.nonNull.list.nonNull.string('links', { resolve() { return [] } });
|
||||
}
|
||||
})
|
||||
|
||||
const Tournament = objectType({
|
||||
name: 'Tournament',
|
||||
definition(t) {
|
||||
t.nonNull.int('id');
|
||||
t.nonNull.string('title');
|
||||
t.nonNull.string('description');
|
||||
t.nonNull.string('thumbnail_image', {
|
||||
async resolve(parent) {
|
||||
return prisma.tournament.findUnique({ where: { id: parent.id } }).thumbnail_image_rel().then(resolveImgObjectToUrl)
|
||||
}
|
||||
});
|
||||
t.nonNull.string('cover_image', {
|
||||
async resolve(parent) {
|
||||
return prisma.tournament.findUnique({ where: { id: parent.id } }).cover_image_rel().then(resolveImgObjectToUrl)
|
||||
}
|
||||
});
|
||||
t.nonNull.date('start_date');
|
||||
t.nonNull.date('end_date');
|
||||
t.nonNull.string('location');
|
||||
t.nonNull.string('website');
|
||||
|
||||
t.nonNull.int('events_count', {
|
||||
resolve(parent) {
|
||||
return prisma.tournamentEvent.count({
|
||||
where: {
|
||||
tournament_id: parent.id
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
t.nonNull.int('makers_count', {
|
||||
resolve(parent) {
|
||||
return prisma.tournamentParticipant.count({
|
||||
where: {
|
||||
tournament_id: parent.id
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
t.nonNull.int('projects_count', {
|
||||
resolve(parent) {
|
||||
return prisma.tournamentProject.count({
|
||||
where: {
|
||||
tournament_id: parent.id
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
t.nonNull.list.nonNull.field('prizes', {
|
||||
type: TournamentPrize,
|
||||
resolve(parent) {
|
||||
return prisma.tournament.findUnique({ where: { id: parent.id } }).prizes()
|
||||
}
|
||||
});
|
||||
t.nonNull.list.nonNull.field('judges', {
|
||||
type: TournamentJudge,
|
||||
resolve(parent) {
|
||||
return prisma.tournament.findUnique({ where: { id: parent.id } }).judges()
|
||||
}
|
||||
});
|
||||
t.nonNull.list.nonNull.field('faqs', {
|
||||
type: TournamentFAQ,
|
||||
resolve(parent) {
|
||||
return prisma.tournament.findUnique({ where: { id: parent.id } }).faqs()
|
||||
}
|
||||
});
|
||||
t.nonNull.list.nonNull.field('events', {
|
||||
type: TournamentEvent,
|
||||
resolve(parent) {
|
||||
return prisma.tournament.findUnique({ where: { id: parent.id } }).events()
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const TournamentMakersResponse = objectType({
|
||||
name: 'TournamentMakersResponse',
|
||||
definition(t) {
|
||||
t.boolean('hasNext');
|
||||
t.boolean('hasPrev');
|
||||
|
||||
t.nonNull.list.nonNull.field('makers', { type: TournamentParticipant })
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const TournamentProjectsResponse = objectType({
|
||||
name: 'TournamentProjectsResponse',
|
||||
definition(t) {
|
||||
t.boolean('hasNext');
|
||||
t.boolean('hasPrev');
|
||||
|
||||
t.nonNull.list.nonNull.field('projects', { type: "Project" })
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const getTournamentById = extendType({
|
||||
type: "Query",
|
||||
definition(t) {
|
||||
t.nonNull.field('getTournamentById', {
|
||||
type: Tournament,
|
||||
args: {
|
||||
id: nonNull(intArg()),
|
||||
},
|
||||
resolve(_, { id }) {
|
||||
return prisma.tournament.findUnique({
|
||||
where: { id }
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const getTournamentToRegister = extendType({
|
||||
type: "Query",
|
||||
definition(t) {
|
||||
t.nonNull.list.nonNull.field('getTournamentToRegister', {
|
||||
type: Tournament,
|
||||
args: {
|
||||
},
|
||||
resolve() {
|
||||
|
||||
return prisma.tournament.findMany({
|
||||
where: {
|
||||
end_date: {
|
||||
gt: new Date()
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const ParticipationInfo = objectType({
|
||||
name: "ParticipationInfo",
|
||||
definition(t) {
|
||||
t.nonNull.date('createdAt')
|
||||
t.nonNull.string('email')
|
||||
t.nonNull.field('hacking_status', { type: TournamentMakerHackingStatusEnum });
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
const tournamentParticipationInfo = extendType({
|
||||
type: "Query",
|
||||
definition(t) {
|
||||
t.field('tournamentParticipationInfo', {
|
||||
type: ParticipationInfo,
|
||||
args: {
|
||||
tournamentId: nonNull(intArg()),
|
||||
},
|
||||
async resolve(_, args, ctx) {
|
||||
|
||||
const user = await getUserByPubKey(ctx.userPubKey);
|
||||
if (!user)
|
||||
return null
|
||||
|
||||
|
||||
return prisma.tournamentParticipant.findFirst({
|
||||
where: {
|
||||
user_id: user.id,
|
||||
tournament_id: args.tournamentId
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const getMakersInTournament = extendType({
|
||||
type: "Query",
|
||||
definition(t) {
|
||||
t.nonNull.field('getMakersInTournament', {
|
||||
type: TournamentMakersResponse,
|
||||
args: {
|
||||
tournamentId: nonNull(intArg()),
|
||||
...paginationArgs({ take: 10 }),
|
||||
search: stringArg(),
|
||||
roleId: intArg(),
|
||||
openToConnect: booleanArg()
|
||||
},
|
||||
async resolve(_, args, ctx) {
|
||||
|
||||
const user = await getUserByPubKey(ctx.userPubKey);
|
||||
|
||||
|
||||
let filters = [];
|
||||
|
||||
if (args.search) filters.push({
|
||||
OR: [
|
||||
{
|
||||
name: {
|
||||
contains: args.search,
|
||||
mode: 'insensitive'
|
||||
}
|
||||
},
|
||||
{
|
||||
jobTitle: {
|
||||
contains: args.search,
|
||||
mode: 'insensitive'
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
|
||||
if (args.roleId) filters.push({
|
||||
roles: {
|
||||
some: {
|
||||
roleId: args.roleId
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (args.openToConnect) filters.push({
|
||||
OR: [
|
||||
{
|
||||
github: {
|
||||
not: ''
|
||||
}
|
||||
},
|
||||
{
|
||||
twitter: {
|
||||
not: ''
|
||||
}
|
||||
},
|
||||
{
|
||||
linkedin: {
|
||||
not: ''
|
||||
}
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
if (user?.id) filters.push({
|
||||
id: {
|
||||
not: user.id
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
|
||||
const makers = (await prisma.tournamentParticipant.findMany({
|
||||
where: {
|
||||
tournament_id: args.tournamentId,
|
||||
...(filters.length > 0 && {
|
||||
user: {
|
||||
AND: filters
|
||||
}
|
||||
}),
|
||||
...(args.openToConnect && {
|
||||
hacking_status: TournamentMakerHackingStatusEnum.value.members.OpenToConnect
|
||||
})
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: 'desc'
|
||||
},
|
||||
include: {
|
||||
user: true,
|
||||
},
|
||||
skip: args.skip,
|
||||
take: args.take + 1,
|
||||
}))
|
||||
.map(item => ({
|
||||
hacking_status: item.hacking_status,
|
||||
user: item.user
|
||||
}))
|
||||
|
||||
|
||||
|
||||
return {
|
||||
hasNext: makers.length === args.take + 1,
|
||||
hasPrev: args.skip !== 0,
|
||||
makers: makers.slice(0, args.take)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const getProjectsInTournament = extendType({
|
||||
type: "Query",
|
||||
definition(t) {
|
||||
t.nonNull.field('getProjectsInTournament', {
|
||||
type: TournamentProjectsResponse,
|
||||
args: {
|
||||
tournamentId: nonNull(intArg()),
|
||||
...paginationArgs({ take: 10 }),
|
||||
search: stringArg(),
|
||||
roleId: intArg(),
|
||||
},
|
||||
async resolve(_, args) {
|
||||
|
||||
|
||||
let filters = [];
|
||||
|
||||
if (args.search) filters.push({
|
||||
OR: [
|
||||
{
|
||||
title: {
|
||||
contains: args.search,
|
||||
mode: 'insensitive'
|
||||
}
|
||||
},
|
||||
{
|
||||
description: {
|
||||
contains: args.search,
|
||||
mode: 'insensitive'
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
|
||||
// if (args.roleId) filters.push({
|
||||
// recruit_roles: {
|
||||
// some: {
|
||||
// roleId: args.roleId
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
|
||||
|
||||
|
||||
const projects = (await prisma.tournamentProject.findMany({
|
||||
where: {
|
||||
tournament_id: args.tournamentId,
|
||||
...(filters.length > 0 && {
|
||||
project: {
|
||||
AND: filters
|
||||
}
|
||||
})
|
||||
},
|
||||
include: {
|
||||
project: true,
|
||||
},
|
||||
skip: args.skip,
|
||||
take: args.take + 1,
|
||||
})).map(item => item.project)
|
||||
|
||||
console.log();
|
||||
|
||||
|
||||
return {
|
||||
hasNext: projects.length === args.take + 1,
|
||||
hasPrev: args.skip !== 0,
|
||||
projects: projects.slice(0, args.take)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
|
||||
const RegisterInTournamentInput = inputObjectType({
|
||||
name: 'RegisterInTournamentInput',
|
||||
definition(t) {
|
||||
t.nonNull.string('email')
|
||||
t.nonNull.field('hacking_status', { type: TournamentMakerHackingStatusEnum })
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const registerInTournament = extendType({
|
||||
type: 'Mutation',
|
||||
definition(t) {
|
||||
t.field('registerInTournament', {
|
||||
type: 'User',
|
||||
args: {
|
||||
data: RegisterInTournamentInput,
|
||||
tournament_id: nonNull(intArg())
|
||||
},
|
||||
async resolve(_root, { tournament_id, data: { email, hacking_status } }, ctx) {
|
||||
const user = await getUserByPubKey(ctx.userPubKey);
|
||||
|
||||
// Do some validation
|
||||
if (!user)
|
||||
throw new Error("You have to login");
|
||||
|
||||
|
||||
// Email verification here:
|
||||
// ....
|
||||
// ....
|
||||
|
||||
return (await prisma.tournamentParticipant.create({
|
||||
data: {
|
||||
tournament_id,
|
||||
user_id: user.id,
|
||||
email,
|
||||
hacking_status
|
||||
},
|
||||
include: {
|
||||
user: true
|
||||
}
|
||||
})).user;
|
||||
}
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
const UpdateTournamentRegistrationInput = inputObjectType({
|
||||
name: 'UpdateTournamentRegistrationInput',
|
||||
definition(t) {
|
||||
t.string('email')
|
||||
t.field('hacking_status', { type: TournamentMakerHackingStatusEnum })
|
||||
}
|
||||
})
|
||||
|
||||
const updateTournamentRegistration = extendType({
|
||||
type: 'Mutation',
|
||||
definition(t) {
|
||||
t.field('updateTournamentRegistration', {
|
||||
type: ParticipationInfo,
|
||||
args: {
|
||||
data: UpdateTournamentRegistrationInput,
|
||||
tournament_id: nonNull(intArg())
|
||||
},
|
||||
async resolve(_root, { tournament_id, data: { email, hacking_status } }, ctx) {
|
||||
const user = await getUserByPubKey(ctx.userPubKey);
|
||||
|
||||
// Do some validation
|
||||
// if (!user)
|
||||
// throw new Error("You have to login");
|
||||
|
||||
|
||||
// Email verification here:
|
||||
// ....
|
||||
// ....
|
||||
|
||||
return prisma.tournamentParticipant.update({
|
||||
where: {
|
||||
tournament_id_user_id: { tournament_id, user_id: user.id }
|
||||
},
|
||||
data: removeNulls({
|
||||
email,
|
||||
hacking_status
|
||||
}),
|
||||
});
|
||||
}
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
module.exports = {
|
||||
// Types
|
||||
Tournament,
|
||||
|
||||
// Enums
|
||||
TournamentEventTypeEnum,
|
||||
|
||||
// Queries
|
||||
getTournamentById,
|
||||
getMakersInTournament,
|
||||
getProjectsInTournament,
|
||||
tournamentParticipationInfo,
|
||||
getTournamentToRegister,
|
||||
|
||||
// Mutations
|
||||
registerInTournament,
|
||||
updateTournamentRegistration,
|
||||
}
|
||||
@@ -1,577 +0,0 @@
|
||||
|
||||
const { prisma } = require('../../../prisma');
|
||||
const { objectType, extendType, intArg, nonNull, inputObjectType, stringArg, interfaceType, list, enumType } = require("nexus");
|
||||
const { getUserByPubKey } = require("../../../auth/utils/helperFuncs");
|
||||
const { removeNulls } = require("./helpers");
|
||||
const { ImageInput } = require('./misc');
|
||||
const { Tournament } = require('./tournament');
|
||||
const { resolveImgObjectToUrl } = require('../../../utils/resolveImageUrl');
|
||||
const { deleteImage } = require('../../../services/imageUpload.service');
|
||||
|
||||
|
||||
|
||||
|
||||
const BaseUser = interfaceType({
|
||||
name: 'BaseUser',
|
||||
definition(t) {
|
||||
t.nonNull.int('id');
|
||||
t.nonNull.string('name');
|
||||
t.nonNull.string('avatar', {
|
||||
async resolve(parent) {
|
||||
return prisma.user.findUnique({ where: { id: parent.id } }).avatar_rel().then(resolveImgObjectToUrl)
|
||||
}
|
||||
});
|
||||
t.nonNull.date('join_date');
|
||||
t.string('role');
|
||||
t.string('jobTitle')
|
||||
t.string('lightning_address')
|
||||
t.string('website')
|
||||
t.string('twitter')
|
||||
t.string('discord')
|
||||
t.string('github')
|
||||
t.string('linkedin')
|
||||
t.string('bio')
|
||||
t.string('location')
|
||||
t.nonNull.list.nonNull.field('roles', {
|
||||
type: MakerRole,
|
||||
resolve: async (parent) => {
|
||||
const data = await prisma.user.findUnique({
|
||||
where: {
|
||||
id: parent.id
|
||||
},
|
||||
select: {
|
||||
roles: {
|
||||
select: {
|
||||
role: true,
|
||||
level: true
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
return data.roles.map(data => {
|
||||
return ({ ...data.role, level: data.level })
|
||||
})
|
||||
}
|
||||
})
|
||||
t.nonNull.list.nonNull.field('skills', {
|
||||
type: MakerSkill,
|
||||
resolve: (parent) => {
|
||||
return prisma.user.findUnique({ where: { id: parent.id } }).skills();
|
||||
}
|
||||
})
|
||||
t.nonNull.list.nonNull.field('tournaments', {
|
||||
type: Tournament,
|
||||
resolve: async (parent) => {
|
||||
return prisma.tournamentParticipant.findMany({
|
||||
where: {
|
||||
user_id: parent.id
|
||||
},
|
||||
include: {
|
||||
tournament: true
|
||||
}
|
||||
}).then(d => d.map(item => item.tournament))
|
||||
}
|
||||
})
|
||||
t.nonNull.list.nonNull.field('projects', {
|
||||
type: "Project",
|
||||
resolve: async (parent) => {
|
||||
return prisma.projectMember.findMany({
|
||||
where: {
|
||||
userId: parent.id
|
||||
},
|
||||
include: {
|
||||
project: true
|
||||
}
|
||||
}).then(d => d.map(item => item.project))
|
||||
}
|
||||
})
|
||||
t.nonNull.list.nonNull.field('similar_makers', {
|
||||
type: "User",
|
||||
resolve(parent,) {
|
||||
return prisma.user.findMany({
|
||||
where: {
|
||||
AND: {
|
||||
id: {
|
||||
not: parent.id
|
||||
}
|
||||
}
|
||||
},
|
||||
take: 3,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
t.nonNull.list.nonNull.field('stories', {
|
||||
type: "Story",
|
||||
resolve: (parent) => {
|
||||
return prisma.story.findMany({ where: { user_id: parent.id, is_published: true }, orderBy: { createdAt: "desc" } });
|
||||
}
|
||||
});
|
||||
|
||||
t.nonNull.boolean('in_tournament', {
|
||||
args: {
|
||||
id: nonNull(intArg())
|
||||
},
|
||||
resolve(parent, args) {
|
||||
return prisma.tournamentParticipant.findFirst({ where: { tournament_id: args.id, user_id: parent.id } }).then(res => !!res)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
},
|
||||
resolveType() {
|
||||
return null
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
|
||||
const RoleLevelEnum = enumType({
|
||||
name: 'RoleLevelEnum',
|
||||
members: {
|
||||
Beginner: 0,
|
||||
Hobbyist: 1,
|
||||
Intermediate: 2,
|
||||
Advanced: 3,
|
||||
Pro: 4,
|
||||
},
|
||||
});
|
||||
|
||||
const GenericMakerRole = objectType({
|
||||
name: 'GenericMakerRole',
|
||||
definition(t) {
|
||||
t.nonNull.int('id');
|
||||
t.nonNull.string('title');
|
||||
t.nonNull.string('icon');
|
||||
}
|
||||
})
|
||||
|
||||
const MakerRole = objectType({
|
||||
name: 'MakerRole',
|
||||
definition(t) {
|
||||
t.nonNull.int('id');
|
||||
t.nonNull.string('title');
|
||||
t.nonNull.string('icon');
|
||||
t.nonNull.field('level', { type: RoleLevelEnum })
|
||||
}
|
||||
})
|
||||
|
||||
const getAllMakersRoles = extendType({
|
||||
type: "Query",
|
||||
definition(t) {
|
||||
t.nonNull.list.nonNull.field('getAllMakersRoles', {
|
||||
type: GenericMakerRole,
|
||||
async resolve(parent, args, context) {
|
||||
return prisma.workRole.findMany();
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const MakerSkill = objectType({
|
||||
name: 'MakerSkill',
|
||||
definition(t) {
|
||||
t.nonNull.int('id');
|
||||
t.nonNull.string('title');
|
||||
}
|
||||
})
|
||||
|
||||
const getAllMakersSkills = extendType({
|
||||
type: "Query",
|
||||
definition(t) {
|
||||
t.nonNull.list.nonNull.field('getAllMakersSkills', {
|
||||
type: MakerSkill,
|
||||
async resolve(parent, args, context) {
|
||||
return prisma.skill.findMany();
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const User = objectType({
|
||||
name: 'User',
|
||||
definition(t) {
|
||||
t.implements('BaseUser')
|
||||
}
|
||||
})
|
||||
|
||||
const MyProfile = objectType({
|
||||
name: 'MyProfile',
|
||||
definition(t) {
|
||||
t.implements('BaseUser')
|
||||
|
||||
t.string('email')
|
||||
t.string('nostr_prv_key')
|
||||
t.string('nostr_pub_key')
|
||||
|
||||
t.nonNull.list.nonNull.field('walletsKeys', {
|
||||
type: "WalletKey",
|
||||
resolve: (parent, _, context) => {
|
||||
return prisma.userKey.findMany({
|
||||
where: {
|
||||
user_id: parent.id,
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: "asc"
|
||||
}
|
||||
})
|
||||
.then(keys => keys.map(k => ({ ...k, is_current: k.key === context.userPubKey })))
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const me = extendType({
|
||||
type: "Query",
|
||||
definition(t) {
|
||||
t.field('me', {
|
||||
type: "MyProfile",
|
||||
async resolve(parent, args, context) {
|
||||
const user = await getUserByPubKey(context.userPubKey)
|
||||
return user
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const profile = extendType({
|
||||
type: "Query",
|
||||
definition(t) {
|
||||
t.field('profile', {
|
||||
type: "User",
|
||||
args: {
|
||||
id: nonNull(intArg())
|
||||
},
|
||||
async resolve(parent, { id }, ctx) {
|
||||
|
||||
const user = await getUserByPubKey(ctx.userPubKey)
|
||||
let isMy = false;
|
||||
if (user?.id === id) isMy = true;
|
||||
|
||||
return prisma.user.findUnique({ where: { id } })
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const searchUsers = extendType({
|
||||
type: "Query",
|
||||
definition(t) {
|
||||
t.nonNull.list.nonNull.field('searchUsers', {
|
||||
type: "User",
|
||||
args: {
|
||||
value: nonNull(stringArg())
|
||||
},
|
||||
async resolve(_, { value }) {
|
||||
return prisma.user.findMany({
|
||||
where: {
|
||||
name: {
|
||||
contains: value,
|
||||
mode: "insensitive"
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const similarMakers = extendType({
|
||||
type: "Query",
|
||||
definition(t) {
|
||||
t.nonNull.list.nonNull.field('similarMakers', {
|
||||
type: "User",
|
||||
args: {
|
||||
id: nonNull(intArg())
|
||||
},
|
||||
async resolve(parent, { id }, ctx) {
|
||||
return prisma.user.findMany({
|
||||
where: {
|
||||
AND: {
|
||||
id: {
|
||||
not: id
|
||||
}
|
||||
}
|
||||
},
|
||||
take: 3,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const ProfileDetailsInput = inputObjectType({
|
||||
name: 'ProfileDetailsInput',
|
||||
definition(t) {
|
||||
t.string('name');
|
||||
t.field('avatar', {
|
||||
type: ImageInput
|
||||
})
|
||||
t.string('email')
|
||||
t.string('jobTitle')
|
||||
t.string('lightning_address')
|
||||
t.string('website')
|
||||
t.string('twitter')
|
||||
t.string('discord')
|
||||
t.string('github')
|
||||
t.string('linkedin')
|
||||
t.string('bio')
|
||||
t.string('location')
|
||||
}
|
||||
})
|
||||
|
||||
const updateProfileDetails = extendType({
|
||||
type: 'Mutation',
|
||||
definition(t) {
|
||||
t.field('updateProfileDetails', {
|
||||
type: 'MyProfile',
|
||||
args: { data: ProfileDetailsInput },
|
||||
async resolve(_root, args, ctx) {
|
||||
const user = await getUserByPubKey(ctx.userPubKey);
|
||||
|
||||
// Do some validation
|
||||
if (!user)
|
||||
throw new Error("You have to login");
|
||||
// TODO: validate new data
|
||||
|
||||
// ----------------
|
||||
// Check if the user uploaded a new image, and if so,
|
||||
// remove the old one from the hosting service, then replace it with this one
|
||||
// ----------------
|
||||
let avatarId = user.avatar_id;
|
||||
if (args.data.avatar.id) {
|
||||
const newAvatarProviderId = args.data.avatar.id;
|
||||
const newAvatar = await prisma.hostedImage.findFirst({
|
||||
where: {
|
||||
provider_image_id: newAvatarProviderId
|
||||
}
|
||||
})
|
||||
|
||||
if (newAvatar && newAvatar.id !== user.avatar_id) {
|
||||
avatarId = newAvatar.id;
|
||||
|
||||
await prisma.hostedImage.update({
|
||||
where: {
|
||||
id: newAvatar.id
|
||||
},
|
||||
data: {
|
||||
is_used: true
|
||||
}
|
||||
});
|
||||
|
||||
await deleteImage(user.avatar_id)
|
||||
}
|
||||
}
|
||||
|
||||
// Preprocess & insert
|
||||
return prisma.user.update({
|
||||
where: {
|
||||
id: user.id,
|
||||
},
|
||||
data: removeNulls({
|
||||
...args.data,
|
||||
avatar_id: avatarId,
|
||||
|
||||
//hack to remove avatar from args.data
|
||||
// can be removed later with a schema data validator
|
||||
avatar: '',
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
const WalletKey = objectType({
|
||||
name: 'WalletKey',
|
||||
definition(t) {
|
||||
t.nonNull.string('key');
|
||||
t.nonNull.string('name');
|
||||
t.nonNull.date('createdAt');
|
||||
t.nonNull.boolean('is_current')
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
|
||||
const UserKeyInputType = inputObjectType({
|
||||
name: 'UserKeyInputType',
|
||||
definition(t) {
|
||||
t.nonNull.string('key');
|
||||
t.nonNull.string('name');
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
|
||||
const updateUserPreferences = extendType({
|
||||
type: 'Mutation',
|
||||
definition(t) {
|
||||
t.nonNull.field('updateUserPreferences', {
|
||||
type: 'MyProfile',
|
||||
args: { userKeys: list(nonNull(UserKeyInputType)) },
|
||||
async resolve(_root, args, ctx) {
|
||||
|
||||
const user = await getUserByPubKey(ctx.userPubKey);
|
||||
if (!user)
|
||||
throw new Error("You have to login");
|
||||
|
||||
|
||||
//Update the userkeys
|
||||
//--------------------
|
||||
|
||||
// Check if all the sent keys belong to the user
|
||||
const userKeys = (await prisma.userKey.findMany({
|
||||
where: {
|
||||
AND: {
|
||||
user_id: {
|
||||
equals: user.id,
|
||||
},
|
||||
key: {
|
||||
in: args.userKeys.map(i => i.key)
|
||||
}
|
||||
},
|
||||
},
|
||||
select: {
|
||||
key: true
|
||||
}
|
||||
})).map(i => i.key);
|
||||
|
||||
const newKeys = [];
|
||||
for (let i = 0; i < args.userKeys.length; i++) {
|
||||
const item = args.userKeys[i];
|
||||
if (userKeys.includes(item.key))
|
||||
newKeys.push(item);
|
||||
}
|
||||
|
||||
|
||||
if (newKeys.length === 0)
|
||||
throw new Error("You can't delete all your wallets keys")
|
||||
|
||||
await prisma.userKey.deleteMany({
|
||||
where: {
|
||||
user_id: user.id
|
||||
}
|
||||
})
|
||||
|
||||
await prisma.userKey.createMany({
|
||||
data: newKeys.map(i => ({
|
||||
user_id: user.id,
|
||||
key: i.key,
|
||||
name: i.name,
|
||||
}))
|
||||
})
|
||||
|
||||
return prisma.user.findUnique({ where: { id: user.id } });
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
|
||||
const MakerRoleInput = inputObjectType({
|
||||
name: "MakerRoleInput",
|
||||
definition(t) {
|
||||
t.nonNull.int('id');
|
||||
t.nonNull.field('level', { type: RoleLevelEnum })
|
||||
}
|
||||
})
|
||||
|
||||
const MakerSkillInput = inputObjectType({
|
||||
name: "MakerSkillInput",
|
||||
definition(t) {
|
||||
t.nonNull.int('id');
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const ProfileRolesInput = inputObjectType({
|
||||
name: 'ProfileRolesInput',
|
||||
definition(t) {
|
||||
t.nonNull.list.nonNull.field('roles', {
|
||||
type: MakerRoleInput,
|
||||
})
|
||||
t.nonNull.list.nonNull.field('skills', {
|
||||
type: MakerSkillInput,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const updateProfileRoles = extendType({
|
||||
type: 'Mutation',
|
||||
definition(t) {
|
||||
t.field('updateProfileRoles', {
|
||||
type: 'MyProfile',
|
||||
args: { data: ProfileRolesInput },
|
||||
async resolve(_root, args, ctx) {
|
||||
const user = await getUserByPubKey(ctx.userPubKey);
|
||||
|
||||
// Do some validation
|
||||
if (!user)
|
||||
throw new Error("You have to login");
|
||||
|
||||
await prisma.user.update({
|
||||
where: {
|
||||
id: user.id,
|
||||
},
|
||||
data: {
|
||||
skills: {
|
||||
set: [],
|
||||
},
|
||||
roles: {
|
||||
deleteMany: {}
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
return prisma.user.update({
|
||||
where: {
|
||||
id: user.id,
|
||||
},
|
||||
data: {
|
||||
skills: {
|
||||
connect: args.data.skills,
|
||||
},
|
||||
roles: {
|
||||
create: args.data.roles.map(r => ({ roleId: r.id, level: r.level }))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
module.exports = {
|
||||
// Types
|
||||
|
||||
BaseUser,
|
||||
User,
|
||||
MyProfile,
|
||||
WalletKey,
|
||||
MakerRole,
|
||||
// Queries
|
||||
me,
|
||||
profile,
|
||||
searchUsers,
|
||||
similarMakers,
|
||||
getAllMakersRoles,
|
||||
getAllMakersSkills,
|
||||
// Mutations
|
||||
updateProfileDetails,
|
||||
updateUserPreferences,
|
||||
updateProfileRoles,
|
||||
}
|
||||
@@ -1,219 +0,0 @@
|
||||
const {
|
||||
intArg,
|
||||
objectType,
|
||||
extendType,
|
||||
nonNull,
|
||||
stringArg,
|
||||
arg,
|
||||
enumType,
|
||||
} = require('nexus')
|
||||
const { parsePaymentRequest } = require('invoices');
|
||||
const { getPaymetRequestForItem, hexToUint8Array } = require('./helpers');
|
||||
const { createHash } = require('crypto');
|
||||
const { prisma } = require('../../../prisma');
|
||||
const { CONSTS } = require('../../../utils');
|
||||
|
||||
|
||||
|
||||
// the types of items we can vote to
|
||||
const VOTE_ITEM_TYPE = enumType({
|
||||
name: 'VOTE_ITEM_TYPE',
|
||||
members: ['Story', 'Bounty', 'Question', 'Project', 'User', 'PostComment'],
|
||||
})
|
||||
|
||||
|
||||
const Vote = objectType({
|
||||
name: 'Vote',
|
||||
definition(t) {
|
||||
t.nonNull.int('id');
|
||||
t.nonNull.int('amount_in_sat');
|
||||
t.nonNull.string('payment_request');
|
||||
t.nonNull.string('payment_hash');
|
||||
t.nonNull.boolean('paid');
|
||||
|
||||
t.nonNull.field('item_type', {
|
||||
type: "VOTE_ITEM_TYPE"
|
||||
})
|
||||
t.nonNull.int('item_id');
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const LnurlDetails = objectType({
|
||||
name: 'LnurlDetails',
|
||||
definition(t) {
|
||||
t.int('minSendable');
|
||||
t.int('maxSendable');
|
||||
t.string('metadata');
|
||||
t.int('commentAllowed');
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
const getModalOfType = (type) => {
|
||||
switch (type) {
|
||||
case "Story":
|
||||
return prisma.story;
|
||||
case "Question":
|
||||
return prisma.question;
|
||||
case "Project":
|
||||
return prisma.project;
|
||||
case "PostComment":
|
||||
return prisma.postComment;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const getLightningAddress = async (item_id, item_type) => {
|
||||
switch (item_type) {
|
||||
case "Story":
|
||||
return prisma.story.findUnique({
|
||||
where: { id: item_id }, include: {
|
||||
user: {
|
||||
select: {
|
||||
lightning_address: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}).then(data => data.user.lightning_address);
|
||||
case "Question":
|
||||
return prisma.question.findUnique({
|
||||
where: { id: item_id }, include: {
|
||||
user: {
|
||||
select: {
|
||||
lightning_address: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}).then(data => data.user.lightning_address);
|
||||
case "Project":
|
||||
return prisma.project.findUnique({
|
||||
where: { id: item_id },
|
||||
select: {
|
||||
lightning_address: true
|
||||
}
|
||||
}).then(data => data.lightning_address);
|
||||
case "Comment":
|
||||
return prisma.postComment.findUnique({
|
||||
where: { id: item_id }, include: {
|
||||
user: {
|
||||
select: {
|
||||
lightning_address: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}).then(data => data.user.lightning_address);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// type => modal
|
||||
// type => lightning address (pr)
|
||||
|
||||
|
||||
// This is the new voting mutation, it can vote for any type of item that we define in the VOTE_ITEM_TYPE enum
|
||||
const voteMutation = extendType({
|
||||
type: "Mutation",
|
||||
definition(t) {
|
||||
t.nonNull.field('vote', {
|
||||
type: "Vote",
|
||||
args: {
|
||||
item_type: arg({
|
||||
type: nonNull("VOTE_ITEM_TYPE")
|
||||
}),
|
||||
item_id: nonNull(intArg()),
|
||||
amount_in_sat: nonNull(intArg())
|
||||
},
|
||||
resolve: async (_, args) => {
|
||||
|
||||
const { item_id, item_type, amount_in_sat } = args;
|
||||
const lightning_address = (await getLightningAddress(item_id, item_type)) ?? CONSTS.BOLT_FUN_LIGHTNING_ADDRESS;
|
||||
const pr = await getPaymetRequestForItem(lightning_address, args.amount_in_sat);
|
||||
const invoice = parsePaymentRequest({ request: pr });
|
||||
|
||||
// #TODO remove votes rows that get added but not confirmed after some time
|
||||
// maybe using a scheduler, timeout, or whatever mean available
|
||||
|
||||
return prisma.vote.create({
|
||||
data: {
|
||||
item_type: item_type,
|
||||
item_id: item_id,
|
||||
amount_in_sat: amount_in_sat,
|
||||
payment_request: pr,
|
||||
payment_hash: invoice.id,
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const confirmVoteMutation = extendType({
|
||||
type: "Mutation",
|
||||
definition(t) {
|
||||
t.nonNull.field('confirmVote', {
|
||||
type: "Vote",
|
||||
args: {
|
||||
payment_request: nonNull(stringArg()),
|
||||
preimage: nonNull(stringArg())
|
||||
},
|
||||
resolve: async (_, args) => {
|
||||
const paymentHash = createHash("sha256")
|
||||
.update(hexToUint8Array(args.preimage))
|
||||
.digest("hex");
|
||||
// look for a vote for the payment request and the calculated payment hash
|
||||
const vote = await prisma.vote.findFirst({
|
||||
where: {
|
||||
payment_request: args.payment_request,
|
||||
payment_hash: paymentHash,
|
||||
},
|
||||
});
|
||||
|
||||
// if we find a vote it means the preimage is correct and we update the vote and mark it as paid
|
||||
// can we write this nicer?
|
||||
if (vote) {
|
||||
const modal = getModalOfType(vote.item_type);
|
||||
const item = await modal.findUnique({
|
||||
where: { id: vote.item_id },
|
||||
});
|
||||
// count up votes cache
|
||||
await modal.update({
|
||||
where: { id: item.id },
|
||||
data: {
|
||||
votes_count: item.votes_count + vote.amount_in_sat,
|
||||
},
|
||||
});
|
||||
// return the current vote
|
||||
return prisma.vote.update({
|
||||
where: { id: vote.id },
|
||||
data: {
|
||||
paid: true,
|
||||
preimage: args.preimage,
|
||||
}
|
||||
});
|
||||
} else {
|
||||
throw new Error("Invalid preimage");
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
module.exports = {
|
||||
// Enums
|
||||
VOTE_ITEM_TYPE,
|
||||
|
||||
// Types
|
||||
Vote,
|
||||
LnurlDetails,
|
||||
|
||||
// Mutations
|
||||
voteMutation,
|
||||
confirmVoteMutation
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
|
||||
const serverless = require('serverless-http');
|
||||
const { createExpressApp } = require('../../modules');
|
||||
const express = require('express');
|
||||
const jose = require('jose');
|
||||
const { JWT_SECRET } = require('../../utils/consts');
|
||||
const lnurlAuthService = require('../../auth/services/lnurlAuth.service');
|
||||
|
||||
|
||||
const isLoggedInHandler = async (req, res) => {
|
||||
try {
|
||||
const login_session = req.headers.session_token;
|
||||
if (login_session) {
|
||||
const { payload } = await jose.jwtVerify(login_session, Buffer.from(JWT_SECRET), {
|
||||
algorithms: ['HS256'],
|
||||
});
|
||||
const hash = payload.hash;
|
||||
const authToken = await lnurlAuthService.getAuthTokenByHash(hash);
|
||||
if (!authToken)
|
||||
throw new Error("Not logged in yet")
|
||||
|
||||
lnurlAuthService.removeHash(hash).catch();
|
||||
lnurlAuthService.removeExpiredHashes().catch();
|
||||
|
||||
res
|
||||
.status(200)
|
||||
.clearCookie('login_session', {
|
||||
secure: true,
|
||||
httpOnly: true,
|
||||
sameSite: "none",
|
||||
})
|
||||
.cookie('Authorization', authToken, {
|
||||
maxAge: 3600000 * 24 * 30,
|
||||
secure: true,
|
||||
httpOnly: true,
|
||||
sameSite: "none",
|
||||
})
|
||||
.json({
|
||||
logged_in: true
|
||||
});
|
||||
} else {
|
||||
res.json({
|
||||
logged_in: false
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
res.json({
|
||||
logged_in: false
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
let app;
|
||||
|
||||
if (process.env.LOCAL) {
|
||||
app = createExpressApp()
|
||||
app.get('/is-logged-in', isLoggedInHandler);
|
||||
}
|
||||
else {
|
||||
const router = express.Router();
|
||||
router.get('/is-logged-in', (isLoggedInHandler))
|
||||
app = createExpressApp(router)
|
||||
}
|
||||
|
||||
|
||||
const handler = serverless(app);
|
||||
exports.handler = async (event, context) => {
|
||||
return await handler(event, context);
|
||||
};
|
||||
@@ -1,170 +0,0 @@
|
||||
|
||||
const { prisma } = require('../../prisma');
|
||||
const LnurlAuthService = require('../../auth/services/lnurlAuth.service')
|
||||
const serverless = require('serverless-http');
|
||||
const { createHash, associateTokenToHash } = require('../../auth/services/lnurlAuth.service');
|
||||
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');
|
||||
const { getUserByPubKey } = require('../../auth/utils/helperFuncs');
|
||||
const { logError } = require('../../utils/logger');
|
||||
|
||||
|
||||
|
||||
const loginHandler = async (req, res) => {
|
||||
|
||||
const { tag, k1, sig, key, action, user_token } = req.query;
|
||||
|
||||
if (tag !== 'login')
|
||||
return res.status(400).json({ status: 'ERROR', reason: 'Invalid Tag Provided' })
|
||||
// Verify login params
|
||||
try {
|
||||
await LnurlAuthService.verifySig(sig, k1, key)
|
||||
} catch (error) {
|
||||
return res.status(400).json({ status: 'ERROR', reason: 'Invalid Signature' })
|
||||
|
||||
}
|
||||
|
||||
if (action === 'link' && user_token) {
|
||||
try {
|
||||
|
||||
let user_id;
|
||||
|
||||
try {
|
||||
const { payload } = await jose.jwtVerify(user_token, Buffer.from(JWT_SECRET), {
|
||||
algorithms: ['HS256'],
|
||||
})
|
||||
user_id = payload.user_id;
|
||||
} catch (error) {
|
||||
return res.status(400).json({ status: 'ERROR', reason: "Invalid user_token" })
|
||||
}
|
||||
|
||||
const existingKeys = await prisma.userKey.findMany({ where: { user_id }, select: { key: true } });
|
||||
|
||||
if (existingKeys.length >= 3)
|
||||
return res.status(400).json({ status: 'ERROR', reason: "Can only link up to 3 wallets" })
|
||||
|
||||
// Remove old linking for this key if existing
|
||||
await prisma.userKey.deleteMany({
|
||||
where: { key }
|
||||
})
|
||||
|
||||
await prisma.userKey.create({
|
||||
data: {
|
||||
key,
|
||||
user_id,
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
return res
|
||||
.status(200)
|
||||
.json({ status: "OK" })
|
||||
|
||||
} catch (error) {
|
||||
logError(error)
|
||||
return res.status(500).json({ status: 'ERROR', reason: 'Unexpected error happened' })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
//Create user if not already existing
|
||||
const user = await getUserByPubKey(key)
|
||||
if (user === null) {
|
||||
|
||||
// Check if user had a previous account using this wallet
|
||||
const oldAccount = await prisma.user.findFirst({
|
||||
where: {
|
||||
pubKey: key
|
||||
}
|
||||
});
|
||||
|
||||
if (oldAccount) {
|
||||
await prisma.userKey.create({
|
||||
data: {
|
||||
key,
|
||||
name: "My original wallet key",
|
||||
user_id: oldAccount.id,
|
||||
}
|
||||
});
|
||||
} else {
|
||||
const nostr_prv_key = generatePrivateKey();
|
||||
const nostr_pub_key = getPublicKey(nostr_prv_key);
|
||||
|
||||
const avatar = await prisma.hostedImage.create({
|
||||
data: {
|
||||
filename: 'avatar.svg',
|
||||
provider: 'external',
|
||||
is_used: true,
|
||||
url: `https://avatars.dicebear.com/api/bottts/${key}.svg`,
|
||||
provider_image_id: ''
|
||||
}
|
||||
})
|
||||
|
||||
const createdUser = await prisma.user.create({
|
||||
data: {
|
||||
pubKey: key,
|
||||
name: key,
|
||||
avatar_id: avatar.id,
|
||||
nostr_prv_key,
|
||||
nostr_pub_key,
|
||||
},
|
||||
})
|
||||
await prisma.userKey.create({
|
||||
data: {
|
||||
key,
|
||||
name: "My original wallet key",
|
||||
user_id: createdUser.id,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// calc the hash of k1
|
||||
const hash = createHash(k1);
|
||||
|
||||
// generate the auth jwt token
|
||||
const hour = 3600000
|
||||
const maxAge = 30 * 24 * hour;
|
||||
|
||||
const authToken = await new jose.SignJWT({ pubKey: key })
|
||||
.setProtectedHeader({ alg: 'HS256' })
|
||||
.setIssuedAt()
|
||||
.setExpirationTime(maxAge)
|
||||
//TODO: Set audience, issuer
|
||||
.sign(Buffer.from(JWT_SECRET, 'utf-8'))
|
||||
|
||||
// associate the auth token with the hash in the db
|
||||
await associateTokenToHash(hash, authToken);
|
||||
|
||||
|
||||
return res.status(200).json({ status: "OK" })
|
||||
|
||||
} catch (error) {
|
||||
logError(error)
|
||||
return res.status(500).json({ status: 'ERROR', reason: 'Unexpected error happened, please try again' })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let app;
|
||||
|
||||
if (process.env.LOCAL) {
|
||||
app = createExpressApp()
|
||||
app.get('/login', loginHandler);
|
||||
}
|
||||
else {
|
||||
const router = express.Router();
|
||||
router.get('/login', loginHandler)
|
||||
app = createExpressApp(router)
|
||||
}
|
||||
|
||||
|
||||
const handler = serverless(app);
|
||||
exports.handler = async (event, context) => {
|
||||
return await handler(event, context);
|
||||
};
|
||||
@@ -1,34 +0,0 @@
|
||||
const serverless = require('serverless-http');
|
||||
const { createExpressApp } = require('../../modules');
|
||||
const express = require('express');
|
||||
|
||||
const logoutHandler = (req, res, next) => {
|
||||
|
||||
res
|
||||
.clearCookie('Authorization', {
|
||||
secure: true,
|
||||
httpOnly: true,
|
||||
sameSite: "none",
|
||||
})
|
||||
.redirect("/")
|
||||
.end()
|
||||
|
||||
};
|
||||
|
||||
let app;
|
||||
|
||||
if (process.env.LOCAL) {
|
||||
app = createExpressApp()
|
||||
app.get('/logout', logoutHandler);
|
||||
}
|
||||
else {
|
||||
const router = express.Router();
|
||||
router.get('/logout', logoutHandler)
|
||||
app = createExpressApp(router)
|
||||
}
|
||||
|
||||
|
||||
const handler = serverless(app);
|
||||
exports.handler = async (event, context) => {
|
||||
return await handler(event, context);
|
||||
};
|
||||
@@ -1,97 +0,0 @@
|
||||
|
||||
const serverless = require('serverless-http');
|
||||
const { createExpressApp } = require('../../modules');
|
||||
const express = require('express');
|
||||
const extractKeyFromCookie = require('../../utils/extractKeyFromCookie');
|
||||
const { getUserByPubKey } = require('../../auth/utils/helperFuncs');
|
||||
const { verifySignature, validateEvent } = require('../../utils/nostr-tools');
|
||||
const { prisma } = require('../../prisma');
|
||||
|
||||
|
||||
const signEvent = async (req, res) => {
|
||||
try {
|
||||
const userPubKey = await extractKeyFromCookie(req.headers.cookie ?? req.headers.Cookie)
|
||||
const user = await getUserByPubKey(userPubKey);
|
||||
|
||||
if (!user)
|
||||
return res.status(401).json({ status: 'ERROR', reason: 'Not Authenticated' });
|
||||
|
||||
const pubkey = user.nostr_pub_key;
|
||||
|
||||
const event = req.body.event
|
||||
|
||||
if (!validateEvent(event))
|
||||
return res.status(400).send("Event not valid");
|
||||
|
||||
if (event.pubkey !== pubkey || !(await verifySignature(event)))
|
||||
return res.status(400).send("Signature not valid")
|
||||
|
||||
|
||||
|
||||
// Extract type & id
|
||||
const rTag = event.tags.find(tag => tag[0] === 'r');
|
||||
const [host, type, refId] = rTag[1].split(' ');
|
||||
|
||||
if (host !== 'boltfun') return res.status(400).send("This event wasn't signed by bolt.fun");
|
||||
|
||||
|
||||
if (type === 'Story_comment') {
|
||||
|
||||
// Extract replyTo id
|
||||
const eTag = event.tags.find(tag => tag[0] === 'e');
|
||||
let parent_comment_id;
|
||||
if (eTag) {
|
||||
const [parent_nostr_comment_id] = eTag[1].split(' ');
|
||||
if (parent_nostr_comment_id)
|
||||
parent_comment_id = (await prisma.postComment.findFirst({
|
||||
where: {
|
||||
nostr_id: parent_nostr_comment_id
|
||||
}
|
||||
}))?.id;
|
||||
}
|
||||
|
||||
|
||||
// Insert comment in database
|
||||
await prisma.postComment.create({
|
||||
data: {
|
||||
nostr_id: event.id,
|
||||
body: event.content,
|
||||
story_id: Number(refId),
|
||||
user_id: user.id,
|
||||
parent_comment_id
|
||||
},
|
||||
|
||||
})
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
return res
|
||||
.status(200)
|
||||
.end()
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
res.status(500).send("Unexpected error happened, please try again")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
let app;
|
||||
|
||||
if (process.env.LOCAL) {
|
||||
app = createExpressApp()
|
||||
app.post('/nostr-confirm-event', signEvent);
|
||||
}
|
||||
else {
|
||||
const router = express.Router();
|
||||
router.post('/nostr-confirm-event', signEvent)
|
||||
app = createExpressApp(router)
|
||||
}
|
||||
|
||||
|
||||
const handler = serverless(app);
|
||||
exports.handler = async (event, context) => {
|
||||
return await handler(event, context);
|
||||
};
|
||||
@@ -1,66 +0,0 @@
|
||||
|
||||
const serverless = require('serverless-http');
|
||||
const { createExpressApp } = require('../../modules');
|
||||
const express = require('express');
|
||||
const { prisma } = require('../../prisma');
|
||||
|
||||
|
||||
const getEventsExtraData = async (req, res) => {
|
||||
try {
|
||||
const ids = req.body.ids ?? []
|
||||
|
||||
const comments = await prisma.postComment.findMany({
|
||||
where: {
|
||||
nostr_id: {
|
||||
in: ids
|
||||
}
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
nostr_id: true,
|
||||
votes_count: true,
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
avatar: true,
|
||||
avatar_rel: {
|
||||
select: {
|
||||
url: true,
|
||||
}
|
||||
},
|
||||
name: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
comments.map(c => c.user.avatar = c.user.avatar_rel?.url ?? c.user.avatar)
|
||||
|
||||
return res
|
||||
.status(200)
|
||||
.json(comments)
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
res.status(500).send("Unexpected error happened, please try again")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
let app;
|
||||
|
||||
if (process.env.LOCAL) {
|
||||
app = createExpressApp()
|
||||
app.post('/nostr-events-extra-data', getEventsExtraData);
|
||||
}
|
||||
else {
|
||||
const router = express.Router();
|
||||
router.post('/nostr-events-extra-data', getEventsExtraData)
|
||||
app = createExpressApp(router)
|
||||
}
|
||||
|
||||
|
||||
const handler = serverless(app);
|
||||
exports.handler = async (event, context) => {
|
||||
return await handler(event, context);
|
||||
};
|
||||
@@ -1,74 +0,0 @@
|
||||
|
||||
const serverless = require('serverless-http');
|
||||
const { createExpressApp } = require('../../modules');
|
||||
const express = require('express');
|
||||
const extractKeyFromCookie = require('../../utils/extractKeyFromCookie');
|
||||
const { getUserByPubKey } = require('../../auth/utils/helperFuncs');
|
||||
const { generatePrivateKey, getPublicKey, signEvent: signNostrEvent } = require('../../utils/nostr-tools');
|
||||
const { prisma } = require('../../prisma');
|
||||
|
||||
|
||||
const signEvent = async (req, res) => {
|
||||
try {
|
||||
const userPubKey = await extractKeyFromCookie(req.headers.cookie ?? req.headers.Cookie)
|
||||
const user = await getUserByPubKey(userPubKey);
|
||||
|
||||
if (!user)
|
||||
return res.status(401).json({ status: 'ERROR', reason: 'Not Authenticated' });
|
||||
|
||||
let prvkey = user.nostr_prv_key, pubkey = user.nostr_pub_key;
|
||||
|
||||
if (!prvkey) {
|
||||
prvkey = generatePrivateKey();
|
||||
pubkey = getPublicKey(prvkey);
|
||||
|
||||
await prisma.user.update({
|
||||
where: {
|
||||
id: user.id,
|
||||
},
|
||||
data: {
|
||||
nostr_prv_key: prvkey,
|
||||
nostr_pub_key: pubkey
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const { content, tags, kind = 1 } = req.body.event
|
||||
const event = {
|
||||
kind,
|
||||
pubkey,
|
||||
content,
|
||||
tags,
|
||||
created_at: Math.round(Date.now() / 1000),
|
||||
}
|
||||
|
||||
event.sig = await signNostrEvent(event, prvkey);
|
||||
|
||||
return res
|
||||
.status(200)
|
||||
.json({ event });
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
res.status(500).send("Unexpected error happened, please try again")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
let app;
|
||||
|
||||
if (process.env.LOCAL) {
|
||||
app = createExpressApp()
|
||||
app.post('/nostr-sign-event', signEvent);
|
||||
}
|
||||
else {
|
||||
const router = express.Router();
|
||||
router.post('/nostr-sign-event', signEvent)
|
||||
app = createExpressApp(router)
|
||||
}
|
||||
|
||||
|
||||
const handler = serverless(app);
|
||||
exports.handler = async (event, context) => {
|
||||
return await handler(event, context);
|
||||
};
|
||||
@@ -1,62 +0,0 @@
|
||||
|
||||
const serverless = require('serverless-http');
|
||||
const { createExpressApp } = require('../../modules');
|
||||
const express = require('express');
|
||||
const extractKeyFromCookie = require('../../utils/extractKeyFromCookie');
|
||||
const { getUserByPubKey } = require('../../auth/utils/helperFuncs');
|
||||
const { prisma } = require('../../prisma');
|
||||
|
||||
|
||||
const mapPubkeysToUsers = async (req, res) => {
|
||||
try {
|
||||
|
||||
const pubkeys = req.body.pubkeys ?? [];
|
||||
|
||||
const usersArr = await prisma.user.findMany({
|
||||
where: {
|
||||
nostr_pub_key: {
|
||||
in: pubkeys
|
||||
}
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
avatar: true,
|
||||
nostr_pub_key: true,
|
||||
lightning_address: true
|
||||
}
|
||||
})
|
||||
|
||||
let pubkeysToUsers = {}
|
||||
usersArr.forEach(user => {
|
||||
pubkeysToUsers[user.nostr_pub_key] = user;
|
||||
});
|
||||
|
||||
return res
|
||||
.status(200)
|
||||
.json({ pubkeysToUsers });
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
res.status(500).send("Unexpected error happened, please try again")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
let app;
|
||||
|
||||
if (process.env.LOCAL) {
|
||||
app = createExpressApp()
|
||||
app.post('/pubkeys-to-users', mapPubkeysToUsers);
|
||||
}
|
||||
else {
|
||||
const router = express.Router();
|
||||
router.post('/pubkeys-to-users', mapPubkeysToUsers)
|
||||
app = createExpressApp(router)
|
||||
}
|
||||
|
||||
|
||||
const handler = serverless(app);
|
||||
exports.handler = async (event, context) => {
|
||||
return await handler(event, context);
|
||||
};
|
||||
@@ -1,53 +0,0 @@
|
||||
const serverless = require('serverless-http')
|
||||
const { createExpressApp } = require('../../modules')
|
||||
const express = require('express')
|
||||
const extractKeyFromCookie = require('../../utils/extractKeyFromCookie')
|
||||
const { getUserByPubKey } = require('../../auth/utils/helperFuncs')
|
||||
const { getDirectUploadUrl } = require('../../services/imageUpload.service')
|
||||
const { prisma } = require('../../prisma')
|
||||
const { getUrlFromProvider } = require('../../utils/resolveImageUrl')
|
||||
|
||||
const postUploadImageUrl = async (req, res) => {
|
||||
|
||||
const userPubKey = await extractKeyFromCookie(req.headers.cookie ?? req.headers.Cookie)
|
||||
const user = await getUserByPubKey(userPubKey)
|
||||
|
||||
if (!user) return res.status(401).json({ status: 'ERROR', reason: 'Not Authenticated' })
|
||||
|
||||
const { filename } = req.body
|
||||
|
||||
if (!filename) return res.status(422).json({ status: 'ERROR', reason: "The field 'filename' is required`" })
|
||||
|
||||
try {
|
||||
const uploadUrl = await getDirectUploadUrl()
|
||||
|
||||
const hostedImage = await prisma.hostedImage.create({
|
||||
data: {
|
||||
filename,
|
||||
url: getUrlFromProvider(uploadUrl.provider, uploadUrl.id),
|
||||
provider_image_id: uploadUrl.id,
|
||||
provider: uploadUrl.provider
|
||||
},
|
||||
})
|
||||
|
||||
return res.status(200).json({ id: hostedImage.id, uploadURL: uploadUrl.uploadURL })
|
||||
} catch (error) {
|
||||
res.status(500).send('Unexpected error happened, please try again')
|
||||
}
|
||||
}
|
||||
|
||||
let app
|
||||
|
||||
if (process.env.LOCAL) {
|
||||
app = createExpressApp()
|
||||
app.post('/upload-image-url', postUploadImageUrl)
|
||||
} else {
|
||||
const router = express.Router()
|
||||
router.post('/upload-image-url', postUploadImageUrl)
|
||||
app = createExpressApp(router)
|
||||
}
|
||||
|
||||
const handler = serverless(app)
|
||||
exports.handler = async (event, context) => {
|
||||
return await handler(event, context)
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
|
||||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
const cookieParser = require('cookie-parser');
|
||||
const bodyParser = require('body-parser')
|
||||
|
||||
|
||||
const createExpressApp = (router) => {
|
||||
|
||||
const app = express();
|
||||
const routerBasePath = process.env.LOCAL ? `/dev` : `/.netlify/functions`
|
||||
|
||||
// parse application/x-www-form-urlencoded
|
||||
app.use(bodyParser.urlencoded({ extended: false }))
|
||||
|
||||
// parse application/json
|
||||
app.use(bodyParser.json())
|
||||
|
||||
app.use(cookieParser());
|
||||
app.use(cors({
|
||||
origin: ['http://localhost:3000', 'https://studio.apollographql.com'],
|
||||
credentials: true,
|
||||
}))
|
||||
|
||||
if (router)
|
||||
app.use(routerBasePath, router);
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
|
||||
module.exports = createExpressApp;
|
||||
@@ -1,6 +0,0 @@
|
||||
const createExpressApp = require("./express-app");
|
||||
|
||||
|
||||
module.exports = {
|
||||
createExpressApp,
|
||||
}
|
||||
3
api/prisma/index.d.ts
vendored
3
api/prisma/index.d.ts
vendored
@@ -1,3 +0,0 @@
|
||||
import { PrismaClient } from '@prisma/client'
|
||||
|
||||
export const prisma: PrismaClient;
|
||||
@@ -1,19 +0,0 @@
|
||||
const { PrismaClient } = process.env.PRISMA_GENERATE_DATAPROXY ? require('@prisma/client/edge') : require('@prisma/client');
|
||||
const createGlobalModule = require('../utils/createGlobalModule');
|
||||
|
||||
|
||||
const createPrismaClient = () => {
|
||||
console.log("New Prisma Client");
|
||||
try {
|
||||
return new PrismaClient();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
const prisma = createGlobalModule('prisma', createPrismaClient)
|
||||
|
||||
|
||||
module.exports = {
|
||||
prisma
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
const { CONSTS } = require('../utils')
|
||||
const axios = require('axios')
|
||||
const FormData = require('form-data')
|
||||
const { prisma } = require('../prisma')
|
||||
|
||||
const BASE_URL = 'https://api.cloudflare.com/client/v4'
|
||||
|
||||
const operationUrls = {
|
||||
'image.uploadUrl': `${BASE_URL}/accounts/${CONSTS.CLOUDFLARE_IMAGE_ACCOUNT_ID}/images/v2/direct_upload`,
|
||||
'image.delete': `${BASE_URL}/accounts/${CONSTS.CLOUDFLARE_IMAGE_ACCOUNT_ID}/images/v1/`,
|
||||
}
|
||||
|
||||
function getAxiosConfig() {
|
||||
return {
|
||||
headers: {
|
||||
Authorization: `Bearer ${CONSTS.CLOUDFLARE_IMAGE_API_KEY}`,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
async function getDirectUploadUrl() {
|
||||
const url = operationUrls['image.uploadUrl']
|
||||
|
||||
const formData = new FormData()
|
||||
formData.append('requireSignedURLs', 'false')
|
||||
|
||||
const config = {
|
||||
headers: {
|
||||
Authorization: `Bearer ${CONSTS.CLOUDFLARE_IMAGE_API_KEY}`,
|
||||
...formData.getHeaders(),
|
||||
},
|
||||
}
|
||||
|
||||
const result = await axios.post(url, formData, config)
|
||||
|
||||
if (!result.data.success) {
|
||||
throw new Error(result.data, { cause: result.data.errors })
|
||||
}
|
||||
|
||||
const data = result.data.result
|
||||
|
||||
return { id: data.id, uploadURL: data.uploadURL, provider: 'cloudflare' }
|
||||
}
|
||||
|
||||
async function deleteImageFromProvider(providerImageId) {
|
||||
try {
|
||||
const url = operationUrls['image.delete'] + providerImageId
|
||||
const result = await axios.delete(url, getAxiosConfig())
|
||||
|
||||
if (!result.data.success) {
|
||||
throw new Error(result.data, { cause: result.data.errors })
|
||||
}
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteImage(hostedImageId) {
|
||||
if (!hostedImageId) throw new Error("argument 'hostedImageId' must be provider")
|
||||
|
||||
const hostedImage = await prisma.hostedImage.findFirst({
|
||||
where: {
|
||||
id: hostedImageId,
|
||||
},
|
||||
})
|
||||
|
||||
if (!hostedImage) throw new Error(`No HostedImage row found for HostedImage.id=${hostedImageId}`)
|
||||
if (hostedImage.provider_image_id && hostedImage.provider_image_id === '')
|
||||
throw new Error(`Field 'provider_image_id' for HostedImage.id=${hostedImageId} must not be empty. Current value '${hostedImage.provider_image_id}'`)
|
||||
|
||||
// Set is_used to false in case of deletion fail from the hosting image provider. The scheduled job will try to delete the HostedImage row
|
||||
await prisma.hostedImage.update({
|
||||
where: {
|
||||
id: hostedImage.id,
|
||||
},
|
||||
data: {
|
||||
is_used: false,
|
||||
},
|
||||
})
|
||||
|
||||
if (hostedImage.provider_image_id && hostedImage.provider_image_id !== '') {
|
||||
deleteImageFromProvider(hostedImage.provider_image_id)
|
||||
.then(async () => {
|
||||
await prisma.hostedImage.delete({
|
||||
where: {
|
||||
id: hostedImageId,
|
||||
},
|
||||
})
|
||||
})
|
||||
.catch((error) => console.error(error))
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getDirectUploadUrl,
|
||||
deleteImage,
|
||||
deleteImageFromProvider,
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
const BOLT_FUN_LIGHTNING_ADDRESS = 'johns@getalby.com' // #TODO, replace it by bolt-fun lightning address if there exist one
|
||||
const JWT_SECRET = process.env.JWT_SECRET
|
||||
const LNURL_AUTH_HOST = process.env.LNURL_AUTH_HOST
|
||||
const CLOUDFLARE_IMAGE_ACCOUNT_ID = process.env.CLOUDFLARE_IMAGE_ACCOUNT_ID
|
||||
const CLOUDFLARE_IMAGE_API_KEY = process.env.CLOUDFLARE_IMAGE_API_KEY
|
||||
const CLOUDFLARE_IMAGE_ACCOUNT_HASH = process.env.CLOUDFLARE_IMAGE_ACCOUNT_HASH
|
||||
|
||||
const CONSTS = {
|
||||
JWT_SECRET,
|
||||
BOLT_FUN_LIGHTNING_ADDRESS,
|
||||
LNURL_AUTH_HOST,
|
||||
CLOUDFLARE_IMAGE_ACCOUNT_ID,
|
||||
CLOUDFLARE_IMAGE_API_KEY,
|
||||
CLOUDFLARE_IMAGE_ACCOUNT_HASH
|
||||
}
|
||||
|
||||
module.exports = CONSTS
|
||||
@@ -1,9 +0,0 @@
|
||||
const createGlobalModule = (name, factoryFn) => {
|
||||
if (!global[name]) {
|
||||
global[name] = factoryFn();
|
||||
}
|
||||
return global[name];
|
||||
}
|
||||
|
||||
module.exports = createGlobalModule
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
|
||||
const cookie = require('cookie')
|
||||
const jose = require('jose');
|
||||
const { JWT_SECRET } = require("./consts");
|
||||
|
||||
const extractKeyFromCookie = async (cookieHeader) => {
|
||||
const cookies = cookie.parse(cookieHeader ?? '');
|
||||
const token = cookies.Authorization;
|
||||
if (token) {
|
||||
try {
|
||||
const { payload } = await jose.jwtVerify(token, Buffer.from(JWT_SECRET), {
|
||||
algorithms: ['HS256'],
|
||||
})
|
||||
return payload.pubKey
|
||||
} catch (error) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
module.exports = extractKeyFromCookie;
|
||||
@@ -1,6 +0,0 @@
|
||||
|
||||
const crypto = require('crypto');
|
||||
|
||||
const generateId = () => crypto.randomUUID({});
|
||||
|
||||
module.exports = generateId;
|
||||
@@ -1,5 +0,0 @@
|
||||
|
||||
module.exports = {
|
||||
CONSTS: require('./consts'),
|
||||
nostr_tools: require('./nostr-tools')
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
|
||||
function logError(error) {
|
||||
console.log("Unexpected Error: ");
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
logError
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
// import * as secp256k1 from '@noble/secp256k1'
|
||||
// import { Buffer } from 'buffer'
|
||||
const secp256k1 = require('@noble/secp256k1');
|
||||
const crypto = require('crypto')
|
||||
|
||||
function generatePrivateKey() {
|
||||
return Buffer.from(secp256k1.utils.randomPrivateKey()).toString('hex')
|
||||
}
|
||||
|
||||
function getPublicKey(privateKey) {
|
||||
return Buffer.from(secp256k1.schnorr.getPublicKey(privateKey)).toString('hex')
|
||||
}
|
||||
|
||||
|
||||
function serializeEvent(evt) {
|
||||
return JSON.stringify([
|
||||
0,
|
||||
evt.pubkey,
|
||||
evt.created_at,
|
||||
evt.kind,
|
||||
evt.tags,
|
||||
evt.content
|
||||
])
|
||||
}
|
||||
|
||||
|
||||
function getEventHash(event) {
|
||||
let eventHash = crypto.createHash('sha256')
|
||||
.update(Buffer.from(serializeEvent(event)))
|
||||
.digest()
|
||||
return Buffer.from(eventHash).toString('hex')
|
||||
}
|
||||
|
||||
|
||||
async function signEvent(event, key) {
|
||||
return Buffer.from(
|
||||
await secp256k1.schnorr.sign(getEventHash(event), key)
|
||||
).toString('hex')
|
||||
}
|
||||
|
||||
function validateEvent(event) {
|
||||
if (event.id !== getEventHash(event)) return false
|
||||
if (typeof event.content !== 'string') return false
|
||||
if (typeof event.created_at !== 'number') return false
|
||||
|
||||
if (!Array.isArray(event.tags)) return false
|
||||
for (let i = 0; i < event.tags.length; i++) {
|
||||
let tag = event.tags[i]
|
||||
if (!Array.isArray(tag)) return false
|
||||
for (let j = 0; j < tag.length; j++) {
|
||||
if (typeof tag[j] === 'object') return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
function verifySignature(event) {
|
||||
return secp256k1.schnorr.verify(event.sig, event.id, event.pubkey)
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
generatePrivateKey,
|
||||
getPublicKey,
|
||||
signEvent,
|
||||
validateEvent,
|
||||
verifySignature,
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
const { CLOUDFLARE_IMAGE_ACCOUNT_HASH } = require('./consts')
|
||||
|
||||
const PROVIDERS = [
|
||||
{
|
||||
name: 'cloudflare',
|
||||
prefixUrl: `https://imagedelivery.net/${CLOUDFLARE_IMAGE_ACCOUNT_HASH}/`,
|
||||
variants: [
|
||||
{
|
||||
default: true,
|
||||
name: 'public',
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
/**
|
||||
* resolveImgObjectToUrl
|
||||
* @param {object} imgObject
|
||||
* @param {string} variant - List to be defined. DEFAULT TO 'public'
|
||||
* @returns {string} image url
|
||||
*/
|
||||
function resolveImgObjectToUrl(imgObject, variant = null) {
|
||||
if (!imgObject) return null;
|
||||
|
||||
if (imgObject.provider === 'external') {
|
||||
return imgObject.url
|
||||
}
|
||||
|
||||
return getUrlFromProvider(imgObject.provider, imgObject.provider_image_id, variant)
|
||||
}
|
||||
|
||||
function getUrlFromProvider(provider, providerImageId, variant = null) {
|
||||
const p = PROVIDERS.find((p) => p.name === provider)
|
||||
|
||||
if (p) {
|
||||
if (p && p.name === 'cloudflare') {
|
||||
const variantName = variant ?? p.variants.find((v) => v.default).name
|
||||
return p.prefixUrl + providerImageId + '/' + variantName
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('Hosting images provider not supported')
|
||||
}
|
||||
|
||||
module.exports = { resolveImgObjectToUrl, getUrlFromProvider }
|
||||
@@ -1,11 +1,7 @@
|
||||
|
||||
|
||||
[build]
|
||||
functions = "api/functions" # netlify-lambda builds to this folder AND Netlify reads functions from here
|
||||
|
||||
[dev]
|
||||
framework = "#static"
|
||||
functions = "api/functions" # netlify-lambda builds to this folder AND Netlify reads functions from here
|
||||
publish = "build" # create-react-app builds to this folder, Netlify should serve all these files statically
|
||||
|
||||
|
||||
19
package.json
19
package.json
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "makers-bolt-fun",
|
||||
"name": "lightning-landscape",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
@@ -99,31 +99,14 @@
|
||||
},
|
||||
"scripts": {
|
||||
"client:dev-server": "env-cmd -f ./envs/client/dev-server.env react-scripts start",
|
||||
"server:dev": "env-cmd -f ./envs/server/local.env serverless offline",
|
||||
"client:preview-server": "env-cmd -f ./envs/client/preview-server.env react-scripts start",
|
||||
"server:preview": "env-cmd -f ./envs/server/preview.env serverless offline",
|
||||
"client:prod-server": "env-cmd -f ./envs/client/prod-server.env react-scripts start",
|
||||
"server:prod": "env-cmd -f ./envs/server/prod.env serverless offline",
|
||||
"client:mocks": "env-cmd -f ./envs/client/mock-server.env react-scripts start",
|
||||
"generate-graphql": "graphql-codegen",
|
||||
"storybook": "env-cmd -f ./envs/client/preview-server.env start-storybook -p 6006 -s public",
|
||||
"storybook:mocks": "env-cmd -f ./envs/client/mock-server.env start-storybook -p 6006 -s public",
|
||||
"build": "react-scripts build",
|
||||
"build:mocks": "env-cmd -f ./envs/client/mock-server.env react-scripts build",
|
||||
"build-storybook": "env-cmd -f ./envs/client/preview-server.env build-storybook -s public",
|
||||
"build-storybook:mocks": "env-cmd -f ./envs/client/mock-server.env build-storybook -s public",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject",
|
||||
"db:migrate-dev": "prisma migrate dev",
|
||||
"db:migrate-deploy": "prisma migrate deploy",
|
||||
"db:reset": "prisma migrate reset",
|
||||
"db:seed": "prisma db seed",
|
||||
"db:gui": "prisma studio",
|
||||
"netlify:start": "set NETLIFY=true&& netlify dev"
|
||||
},
|
||||
"prisma": {
|
||||
"seed": "node prisma/seed/index.js"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "Category" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "Category_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Project" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"description" TEXT NOT NULL,
|
||||
"website" TEXT NOT NULL,
|
||||
"thumbnail_image" TEXT,
|
||||
"cover_image" TEXT,
|
||||
"category_id" INTEGER NOT NULL,
|
||||
"votes_count" INTEGER NOT NULL DEFAULT 0,
|
||||
"lightning_address" TEXT,
|
||||
|
||||
CONSTRAINT "Project_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Vote" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"project_id" INTEGER NOT NULL,
|
||||
"amount_in_sat" INTEGER NOT NULL,
|
||||
"payment_request" TEXT,
|
||||
"payment_hash" TEXT,
|
||||
"preimage" TEXT,
|
||||
"paid" BOOLEAN NOT NULL DEFAULT false,
|
||||
|
||||
CONSTRAINT "Vote_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Project" ADD CONSTRAINT "Project_category_id_fkey" FOREIGN KEY ("category_id") REFERENCES "Category"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Vote" ADD CONSTRAINT "Vote_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "Project"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
@@ -1,2 +0,0 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Project" ADD COLUMN "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;
|
||||
@@ -1,2 +0,0 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Project" ADD COLUMN "lnurl_callback_url" TEXT;
|
||||
@@ -1,20 +0,0 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Category" ADD COLUMN "cover_image" TEXT,
|
||||
ADD COLUMN "icon" TEXT;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Project" ADD COLUMN "screenshots" TEXT[];
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Award" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"cover_image" TEXT NOT NULL,
|
||||
"icon" TEXT NOT NULL,
|
||||
"project_id" INTEGER NOT NULL,
|
||||
|
||||
CONSTRAINT "Award_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Award" ADD CONSTRAINT "Award_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "Project"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
@@ -1,14 +0,0 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `cover_image` on the `Award` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `icon` on the `Award` table. All the data in the column will be lost.
|
||||
- Added the required column `image` to the `Award` table without a default value. This is not possible if the table is not empty.
|
||||
- Added the required column `url` to the `Award` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "Award" DROP COLUMN "cover_image",
|
||||
DROP COLUMN "icon",
|
||||
ADD COLUMN "image" TEXT NOT NULL,
|
||||
ADD COLUMN "url" TEXT NOT NULL;
|
||||
@@ -1,25 +0,0 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "Tag" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "Tag_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "_ProjectToTag" (
|
||||
"A" INTEGER NOT NULL,
|
||||
"B" INTEGER NOT NULL
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "_ProjectToTag_AB_unique" ON "_ProjectToTag"("A", "B");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "_ProjectToTag_B_index" ON "_ProjectToTag"("B");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_ProjectToTag" ADD FOREIGN KEY ("A") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_ProjectToTag" ADD FOREIGN KEY ("B") REFERENCES "Tag"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -1,8 +0,0 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- A unique constraint covering the columns `[title]` on the table `Tag` will be added. If there are existing duplicate values, this will fail.
|
||||
|
||||
*/
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Tag_title_key" ON "Tag"("title");
|
||||
@@ -1,159 +0,0 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "User" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"username" TEXT NOT NULL,
|
||||
"lightning_address" TEXT,
|
||||
"avatar" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Story" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"date" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"body" TEXT NOT NULL,
|
||||
"thumbnail_image" TEXT NOT NULL,
|
||||
"cover_image" TEXT NOT NULL,
|
||||
"votes_count" INTEGER NOT NULL DEFAULT 0,
|
||||
"topic_id" INTEGER NOT NULL,
|
||||
"user_id" INTEGER,
|
||||
|
||||
CONSTRAINT "Story_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Question" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"date" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"body" TEXT NOT NULL,
|
||||
"thumbnail_image" TEXT NOT NULL,
|
||||
"votes_count" INTEGER NOT NULL DEFAULT 0,
|
||||
"topic_id" INTEGER NOT NULL,
|
||||
"user_id" INTEGER,
|
||||
|
||||
CONSTRAINT "Question_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Topic" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"icon" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "Topic_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "PostComment" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"body" TEXT NOT NULL,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"votes_count" INTEGER NOT NULL DEFAULT 0,
|
||||
"parent_comment_id" INTEGER,
|
||||
"user_id" INTEGER,
|
||||
"story_id" INTEGER,
|
||||
"question_id" INTEGER,
|
||||
|
||||
CONSTRAINT "PostComment_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Hackathon" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"date" TEXT NOT NULL,
|
||||
"cover_image" TEXT NOT NULL,
|
||||
"description" TEXT NOT NULL,
|
||||
"location" TEXT NOT NULL,
|
||||
"website" TEXT NOT NULL,
|
||||
"votes_count" INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
CONSTRAINT "Hackathon_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "_StoryToTag" (
|
||||
"A" INTEGER NOT NULL,
|
||||
"B" INTEGER NOT NULL
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "_QuestionToTag" (
|
||||
"A" INTEGER NOT NULL,
|
||||
"B" INTEGER NOT NULL
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "_HackathonToTopic" (
|
||||
"A" INTEGER NOT NULL,
|
||||
"B" INTEGER NOT NULL
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "User_username_key" ON "User"("username");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Topic_title_key" ON "Topic"("title");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "_StoryToTag_AB_unique" ON "_StoryToTag"("A", "B");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "_StoryToTag_B_index" ON "_StoryToTag"("B");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "_QuestionToTag_AB_unique" ON "_QuestionToTag"("A", "B");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "_QuestionToTag_B_index" ON "_QuestionToTag"("B");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "_HackathonToTopic_AB_unique" ON "_HackathonToTopic"("A", "B");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "_HackathonToTopic_B_index" ON "_HackathonToTopic"("B");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Story" ADD CONSTRAINT "Story_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Story" ADD CONSTRAINT "Story_topic_id_fkey" FOREIGN KEY ("topic_id") REFERENCES "Topic"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Question" ADD CONSTRAINT "Question_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Question" ADD CONSTRAINT "Question_topic_id_fkey" FOREIGN KEY ("topic_id") REFERENCES "Topic"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "PostComment" ADD CONSTRAINT "PostComment_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "PostComment" ADD CONSTRAINT "PostComment_story_id_fkey" FOREIGN KEY ("story_id") REFERENCES "Story"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "PostComment" ADD CONSTRAINT "PostComment_question_id_fkey" FOREIGN KEY ("question_id") REFERENCES "Question"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "PostComment" ADD CONSTRAINT "PostComment_parent_comment_id_fkey" FOREIGN KEY ("parent_comment_id") REFERENCES "PostComment"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_StoryToTag" ADD FOREIGN KEY ("A") REFERENCES "Story"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_StoryToTag" ADD FOREIGN KEY ("B") REFERENCES "Tag"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_QuestionToTag" ADD FOREIGN KEY ("A") REFERENCES "Question"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_QuestionToTag" ADD FOREIGN KEY ("B") REFERENCES "Tag"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_HackathonToTopic" ADD FOREIGN KEY ("A") REFERENCES "Hackathon"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_HackathonToTopic" ADD FOREIGN KEY ("B") REFERENCES "Topic"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -1,23 +0,0 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `created_at` on the `PostComment` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `date` on the `Question` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `date` on the `Story` table. All the data in the column will be lost.
|
||||
- Added the required column `updatedAt` to the `Question` table without a default value. This is not possible if the table is not empty.
|
||||
- Added the required column `updatedAt` to the `Story` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "PostComment" DROP COLUMN "created_at",
|
||||
ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Question" DROP COLUMN "date",
|
||||
ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Story" DROP COLUMN "date",
|
||||
ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL;
|
||||
@@ -1,20 +0,0 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `date` on the `Hackathon` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `thumbnail_image` on the `Question` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `thumbnail_image` on the `Story` table. All the data in the column will be lost.
|
||||
- Added the required column `end_date` to the `Hackathon` table without a default value. This is not possible if the table is not empty.
|
||||
- Added the required column `start_date` to the `Hackathon` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "Hackathon" DROP COLUMN "date",
|
||||
ADD COLUMN "end_date" DATE NOT NULL,
|
||||
ADD COLUMN "start_date" DATE NOT NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Question" DROP COLUMN "thumbnail_image";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Story" DROP COLUMN "thumbnail_image";
|
||||
@@ -1,9 +0,0 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `created_at` on the `Project` table. All the data in the column will be lost.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "Project" DROP COLUMN "created_at",
|
||||
ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;
|
||||
@@ -1,17 +0,0 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `username` on the `User` table. All the data in the column will be lost.
|
||||
- A unique constraint covering the columns `[name]` on the table `User` will be added. If there are existing duplicate values, this will fail.
|
||||
- Added the required column `name` to the `User` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- DropIndex
|
||||
DROP INDEX "User_username_key";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "User" DROP COLUMN "username",
|
||||
ADD COLUMN "name" TEXT NOT NULL;
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "User_name_key" ON "User"("name");
|
||||
@@ -1,12 +0,0 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- Added the required column `excerpt` to the `Question` table without a default value. This is not possible if the table is not empty.
|
||||
- Added the required column `excerpt` to the `Story` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "Question" ADD COLUMN "excerpt" TEXT NOT NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Story" ADD COLUMN "excerpt" TEXT NOT NULL;
|
||||
@@ -1,15 +0,0 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `project_id` on the `Vote` table. All the data in the column will be lost.
|
||||
- Added the required column `item_id` to the `Vote` table without a default value. This is not possible if the table is not empty.
|
||||
- Added the required column `item_type` to the `Vote` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "Vote" DROP CONSTRAINT "Vote_project_id_fkey";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Vote" DROP COLUMN "project_id",
|
||||
ADD COLUMN "item_id" INTEGER NOT NULL,
|
||||
ADD COLUMN "item_type" TEXT NOT NULL;
|
||||
@@ -1,16 +0,0 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "Donation" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"amount" INTEGER NOT NULL,
|
||||
"createdAt" DATE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"payment_request" TEXT,
|
||||
"payment_hash" TEXT,
|
||||
"preimage" TEXT,
|
||||
"paid" BOOLEAN NOT NULL DEFAULT false,
|
||||
"donor_id" INTEGER,
|
||||
|
||||
CONSTRAINT "Donation_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Donation" ADD CONSTRAINT "Donation_donor_id_fkey" FOREIGN KEY ("donor_id") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
@@ -1,23 +0,0 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- A unique constraint covering the columns `[pubKey]` on the table `User` will be added. If there are existing duplicate values, this will fail.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "User" ADD COLUMN "email" TEXT,
|
||||
ADD COLUMN "pubKey" TEXT,
|
||||
ADD COLUMN "website" TEXT,
|
||||
ALTER COLUMN "avatar" DROP NOT NULL,
|
||||
ALTER COLUMN "name" DROP NOT NULL;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "GeneratedK1" (
|
||||
"value" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "GeneratedK1_pkey" PRIMARY KEY ("value")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "User_pubKey_key" ON "User"("pubKey");
|
||||
@@ -1,2 +0,0 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "User" ADD COLUMN "join_date" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;
|
||||
@@ -1,6 +0,0 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "User" ADD COLUMN "bio" TEXT,
|
||||
ADD COLUMN "github" TEXT,
|
||||
ADD COLUMN "location" TEXT,
|
||||
ADD COLUMN "role" TEXT NOT NULL DEFAULT E'user',
|
||||
ADD COLUMN "twitter" TEXT;
|
||||
@@ -1,3 +0,0 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "User" ADD COLUMN "jobTitle" TEXT,
|
||||
ADD COLUMN "linkedin" TEXT;
|
||||
@@ -1,2 +0,0 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "GeneratedK1" ADD COLUMN "sid" TEXT;
|
||||
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the `Topic` table. If the table is not empty, all the data it contains will be lost.
|
||||
- You are about to drop the `_HackathonToTopic` table. If the table is not empty, all the data it contains will be lost.
|
||||
|
||||
*/
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "Question" DROP CONSTRAINT "Question_topic_id_fkey";
|
||||
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "Story" DROP CONSTRAINT "Story_topic_id_fkey";
|
||||
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "_HackathonToTopic" DROP CONSTRAINT "_HackathonToTopic_A_fkey";
|
||||
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "_HackathonToTopic" DROP CONSTRAINT "_HackathonToTopic_B_fkey";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Tag" ADD COLUMN "icon" TEXT,
|
||||
ADD COLUMN "isOfficial" BOOLEAN NOT NULL DEFAULT false;
|
||||
|
||||
-- DropTable
|
||||
DROP TABLE "Topic";
|
||||
|
||||
-- DropTable
|
||||
DROP TABLE "_HackathonToTopic";
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "_HackathonToTag" (
|
||||
"A" INTEGER NOT NULL,
|
||||
"B" INTEGER NOT NULL
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "_HackathonToTag_AB_unique" ON "_HackathonToTag"("A", "B");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "_HackathonToTag_B_index" ON "_HackathonToTag"("B");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_HackathonToTag" ADD FOREIGN KEY ("A") REFERENCES "Hackathon"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_HackathonToTag" ADD FOREIGN KEY ("B") REFERENCES "Tag"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -1,12 +0,0 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `topic_id` on the `Question` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `topic_id` on the `Story` table. All the data in the column will be lost.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "Question" DROP COLUMN "topic_id";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Story" DROP COLUMN "topic_id";
|
||||
@@ -1,2 +0,0 @@
|
||||
-- DropIndex
|
||||
DROP INDEX "User_name_key";
|
||||
@@ -1,2 +0,0 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Story" ALTER COLUMN "cover_image" DROP NOT NULL;
|
||||
@@ -1,2 +0,0 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Tag" ADD COLUMN "description" TEXT;
|
||||
@@ -1,2 +0,0 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Story" ADD COLUMN "is_published" BOOLEAN NOT NULL DEFAULT false;
|
||||
@@ -1,2 +0,0 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Story" ALTER COLUMN "is_published" SET DEFAULT true;
|
||||
@@ -1,6 +0,0 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Question" ADD COLUMN "is_published" BOOLEAN NOT NULL DEFAULT true;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "User" ADD COLUMN "nostr_prv_key" TEXT,
|
||||
ADD COLUMN "nostr_pub_key" TEXT;
|
||||
@@ -1,9 +0,0 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `createdAt` on the `PostComment` table. All the data in the column will be lost.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "PostComment" DROP COLUMN "createdAt",
|
||||
ADD COLUMN "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;
|
||||
@@ -1,19 +0,0 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- The primary key for the `PostComment` table will be changed. If it partially fails, the table could be left without primary key constraint.
|
||||
|
||||
*/
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "PostComment" DROP CONSTRAINT "PostComment_parent_comment_id_fkey";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "PostComment" DROP CONSTRAINT "PostComment_pkey",
|
||||
ALTER COLUMN "id" DROP DEFAULT,
|
||||
ALTER COLUMN "id" SET DATA TYPE TEXT,
|
||||
ALTER COLUMN "parent_comment_id" SET DATA TYPE TEXT,
|
||||
ADD CONSTRAINT "PostComment_pkey" PRIMARY KEY ("id");
|
||||
DROP SEQUENCE "PostComment_id_seq";
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "PostComment" ADD CONSTRAINT "PostComment_parent_comment_id_fkey" FOREIGN KEY ("parent_comment_id") REFERENCES "PostComment"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
@@ -1,22 +0,0 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- The primary key for the `PostComment` table will be changed. If it partially fails, the table could be left without primary key constraint.
|
||||
- The `id` column on the `PostComment` table would be dropped and recreated. This will lead to data loss if there is data in the column.
|
||||
- The `parent_comment_id` column on the `PostComment` table would be dropped and recreated. This will lead to data loss if there is data in the column.
|
||||
|
||||
*/
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "PostComment" DROP CONSTRAINT "PostComment_parent_comment_id_fkey";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "PostComment" DROP CONSTRAINT "PostComment_pkey",
|
||||
ADD COLUMN "nostr_id" TEXT,
|
||||
DROP COLUMN "id",
|
||||
ADD COLUMN "id" SERIAL NOT NULL,
|
||||
DROP COLUMN "parent_comment_id",
|
||||
ADD COLUMN "parent_comment_id" INTEGER,
|
||||
ADD CONSTRAINT "PostComment_pkey" PRIMARY KEY ("id");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "PostComment" ADD CONSTRAINT "PostComment_parent_comment_id_fkey" FOREIGN KEY ("parent_comment_id") REFERENCES "PostComment"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
@@ -1,10 +0,0 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "UserKey" (
|
||||
"key" TEXT NOT NULL,
|
||||
"user_id" INTEGER,
|
||||
|
||||
CONSTRAINT "UserKey_pkey" PRIMARY KEY ("key")
|
||||
);
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "UserKey" ADD CONSTRAINT "UserKey_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
@@ -1,2 +0,0 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "UserKey" ADD COLUMN "name" TEXT NOT NULL DEFAULT E'New Key Name';
|
||||
@@ -1,57 +0,0 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "UserKey" ALTER COLUMN "name" SET DEFAULT E'My new wallet key';
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "UsersOnWorkRoles" (
|
||||
"userId" INTEGER NOT NULL,
|
||||
"roleId" INTEGER NOT NULL,
|
||||
|
||||
CONSTRAINT "UsersOnWorkRoles_pkey" PRIMARY KEY ("userId","roleId")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "WorkRole" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"icon" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "WorkRole_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Skill" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "Skill_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "_SkillToUser" (
|
||||
"A" INTEGER NOT NULL,
|
||||
"B" INTEGER NOT NULL
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "WorkRole_title_key" ON "WorkRole"("title");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Skill_title_key" ON "Skill"("title");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "_SkillToUser_AB_unique" ON "_SkillToUser"("A", "B");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "_SkillToUser_B_index" ON "_SkillToUser"("B");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "UsersOnWorkRoles" ADD CONSTRAINT "UsersOnWorkRoles_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "UsersOnWorkRoles" ADD CONSTRAINT "UsersOnWorkRoles_roleId_fkey" FOREIGN KEY ("roleId") REFERENCES "WorkRole"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_SkillToUser" ADD FOREIGN KEY ("A") REFERENCES "Skill"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_SkillToUser" ADD FOREIGN KEY ("B") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -1,8 +0,0 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- Added the required column `level` to the `UsersOnWorkRoles` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "UsersOnWorkRoles" ADD COLUMN "level" TEXT NOT NULL;
|
||||
@@ -1,9 +0,0 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- Changed the type of `level` on the `UsersOnWorkRoles` table. No cast exists, the column would be dropped and recreated, which cannot be done if there is data, since the column is required.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "UsersOnWorkRoles" DROP COLUMN "level",
|
||||
ADD COLUMN "level" INTEGER NOT NULL;
|
||||
@@ -1,9 +0,0 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "HostedImage" (
|
||||
"id" TEXT NOT NULL,
|
||||
"filename" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"is_used" BOOLEAN NOT NULL DEFAULT false,
|
||||
|
||||
CONSTRAINT "HostedImage_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
@@ -1,29 +0,0 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- The primary key for the `HostedImage` table will be changed. If it partially fails, the table could be left without primary key constraint.
|
||||
- The `id` column on the `HostedImage` table would be dropped and recreated. This will lead to data loss if there is data in the column.
|
||||
- Added the required column `provider` to the `HostedImage` table without a default value. This is not possible if the table is not empty.
|
||||
- Added the required column `provider_image_id` to the `HostedImage` table without a default value. This is not possible if the table is not empty.
|
||||
- Added the required column `url` to the `HostedImage` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
|
||||
-- START Custom SQL
|
||||
-- Because of the breaking changes, we have to apply some custom SQL.
|
||||
-- By chance, the previous HostedImage migration is not used it production, so we can remove all data from this table
|
||||
DELETE FROM "HostedImage";
|
||||
-- END Custom SQL
|
||||
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "HostedImage" DROP CONSTRAINT "HostedImage_pkey",
|
||||
ADD COLUMN "provider" TEXT NOT NULL,
|
||||
ADD COLUMN "provider_image_id" TEXT NOT NULL,
|
||||
ADD COLUMN "url" TEXT NOT NULL,
|
||||
DROP COLUMN "id",
|
||||
ADD COLUMN "id" SERIAL NOT NULL,
|
||||
ADD CONSTRAINT "HostedImage_pkey" PRIMARY KEY ("id");
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "UserKey" ALTER COLUMN "name" SET DEFAULT E'My new wallet key';
|
||||
@@ -1,14 +0,0 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "ProjectRecruitRoles" (
|
||||
"projectId" INTEGER NOT NULL,
|
||||
"roleId" INTEGER NOT NULL,
|
||||
"level" INTEGER NOT NULL,
|
||||
|
||||
CONSTRAINT "ProjectRecruitRoles_pkey" PRIMARY KEY ("projectId","roleId")
|
||||
);
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "ProjectRecruitRoles" ADD CONSTRAINT "ProjectRecruitRoles_roleId_fkey" FOREIGN KEY ("roleId") REFERENCES "WorkRole"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "ProjectRecruitRoles" ADD CONSTRAINT "ProjectRecruitRoles_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
@@ -1,102 +0,0 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "Tournament" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"description" TEXT NOT NULL,
|
||||
"thumbnail_image" TEXT NOT NULL,
|
||||
"cover_image" TEXT NOT NULL,
|
||||
"start_date" DATE NOT NULL,
|
||||
"end_date" DATE NOT NULL,
|
||||
"location" TEXT NOT NULL,
|
||||
"website" TEXT NOT NULL,
|
||||
"votes_count" INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
CONSTRAINT "Tournament_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "TournamentPrize" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"amount" TEXT NOT NULL,
|
||||
"image" TEXT NOT NULL,
|
||||
"tournament_id" INTEGER NOT NULL,
|
||||
|
||||
CONSTRAINT "TournamentPrize_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "TournamentJudge" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"company" TEXT NOT NULL,
|
||||
"twitter" TEXT,
|
||||
"tournament_id" INTEGER NOT NULL,
|
||||
|
||||
CONSTRAINT "TournamentJudge_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "TournamentFAQ" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"question" TEXT NOT NULL,
|
||||
"answer" TEXT NOT NULL,
|
||||
"tournament_id" INTEGER NOT NULL,
|
||||
|
||||
CONSTRAINT "TournamentFAQ_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "TournamentEvent" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"image" TEXT NOT NULL,
|
||||
"description" TEXT NOT NULL,
|
||||
"date" DATE NOT NULL,
|
||||
"location" TEXT NOT NULL,
|
||||
"website" TEXT NOT NULL,
|
||||
"type" INTEGER NOT NULL,
|
||||
"tournament_id" INTEGER NOT NULL,
|
||||
|
||||
CONSTRAINT "TournamentEvent_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "TournamentParticipant" (
|
||||
"tournament_id" INTEGER NOT NULL,
|
||||
"user_id" INTEGER NOT NULL,
|
||||
|
||||
CONSTRAINT "TournamentParticipant_pkey" PRIMARY KEY ("tournament_id","user_id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "TournamentProject" (
|
||||
"tournament_id" INTEGER NOT NULL,
|
||||
"project_id" INTEGER NOT NULL,
|
||||
|
||||
CONSTRAINT "TournamentProject_pkey" PRIMARY KEY ("tournament_id","project_id")
|
||||
);
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "TournamentPrize" ADD CONSTRAINT "TournamentPrize_tournament_id_fkey" FOREIGN KEY ("tournament_id") REFERENCES "Tournament"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "TournamentJudge" ADD CONSTRAINT "TournamentJudge_tournament_id_fkey" FOREIGN KEY ("tournament_id") REFERENCES "Tournament"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "TournamentFAQ" ADD CONSTRAINT "TournamentFAQ_tournament_id_fkey" FOREIGN KEY ("tournament_id") REFERENCES "Tournament"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "TournamentEvent" ADD CONSTRAINT "TournamentEvent_tournament_id_fkey" FOREIGN KEY ("tournament_id") REFERENCES "Tournament"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "TournamentParticipant" ADD CONSTRAINT "TournamentParticipant_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "TournamentParticipant" ADD CONSTRAINT "TournamentParticipant_tournament_id_fkey" FOREIGN KEY ("tournament_id") REFERENCES "Tournament"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "TournamentProject" ADD CONSTRAINT "TournamentProject_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "Project"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "TournamentProject" ADD CONSTRAINT "TournamentProject_tournament_id_fkey" FOREIGN KEY ("tournament_id") REFERENCES "Tournament"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
@@ -1,12 +0,0 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `date` on the `TournamentEvent` table. All the data in the column will be lost.
|
||||
- Added the required column `ends_at` to the `TournamentEvent` table without a default value. This is not possible if the table is not empty.
|
||||
- Added the required column `starts_at` to the `TournamentEvent` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "TournamentEvent" DROP COLUMN "date",
|
||||
ADD COLUMN "ends_at" DATE NOT NULL,
|
||||
ADD COLUMN "starts_at" DATE NOT NULL;
|
||||
@@ -1,8 +0,0 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- Added the required column `avatar` to the `TournamentJudge` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "TournamentJudge" ADD COLUMN "avatar" TEXT NOT NULL;
|
||||
@@ -1,10 +0,0 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- Added the required column `email` to the `TournamentParticipant` table without a default value. This is not possible if the table is not empty.
|
||||
- Added the required column `hacking_status` to the `TournamentParticipant` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "TournamentParticipant" ADD COLUMN "email" TEXT NOT NULL,
|
||||
ADD COLUMN "hacking_status" INTEGER NOT NULL;
|
||||
@@ -1,5 +0,0 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "TournamentParticipant" ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "UserKey" ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;
|
||||
@@ -1,2 +0,0 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "User" ADD COLUMN "discord" TEXT;
|
||||
@@ -1,10 +0,0 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Donation" ALTER COLUMN "createdAt" SET DATA TYPE TIMESTAMP(3);
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Tournament" ALTER COLUMN "start_date" SET DATA TYPE TIMESTAMP(3),
|
||||
ALTER COLUMN "end_date" SET DATA TYPE TIMESTAMP(3);
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "TournamentEvent" ALTER COLUMN "ends_at" SET DATA TYPE TIMESTAMP(3),
|
||||
ALTER COLUMN "starts_at" SET DATA TYPE TIMESTAMP(3);
|
||||
@@ -1,74 +0,0 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- A unique constraint covering the columns `[image_id]` on the table `Award` will be added. If there are existing duplicate values, this will fail.
|
||||
- A unique constraint covering the columns `[cover_image_id]` on the table `Category` will be added. If there are existing duplicate values, this will fail.
|
||||
- A unique constraint covering the columns `[cover_image_id]` on the table `Hackathon` will be added. If there are existing duplicate values, this will fail.
|
||||
- A unique constraint covering the columns `[thumbnail_image_id]` on the table `Project` will be added. If there are existing duplicate values, this will fail.
|
||||
- A unique constraint covering the columns `[cover_image_id]` on the table `Project` will be added. If there are existing duplicate values, this will fail.
|
||||
- A unique constraint covering the columns `[cover_image_id]` on the table `Story` will be added. If there are existing duplicate values, this will fail.
|
||||
- A unique constraint covering the columns `[avatar_id]` on the table `User` will be added. If there are existing duplicate values, this will fail.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "Award" ADD COLUMN "image_id" INTEGER;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Category" ADD COLUMN "cover_image_id" INTEGER;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Hackathon" ADD COLUMN "cover_image_id" INTEGER;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Project" ADD COLUMN "cover_image_id" INTEGER,
|
||||
ADD COLUMN "screenshots_ids" INTEGER[],
|
||||
ADD COLUMN "thumbnail_image_id" INTEGER;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Story" ADD COLUMN "body_image_ids" INTEGER[],
|
||||
ADD COLUMN "cover_image_id" INTEGER;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "User" ADD COLUMN "avatar_id" INTEGER;
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Award_image_id_key" ON "Award"("image_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Category_cover_image_id_key" ON "Category"("cover_image_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Hackathon_cover_image_id_key" ON "Hackathon"("cover_image_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Project_thumbnail_image_id_key" ON "Project"("thumbnail_image_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Project_cover_image_id_key" ON "Project"("cover_image_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Story_cover_image_id_key" ON "Story"("cover_image_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "User_avatar_id_key" ON "User"("avatar_id");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "User" ADD CONSTRAINT "User_avatar_id_fkey" FOREIGN KEY ("avatar_id") REFERENCES "HostedImage"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Category" ADD CONSTRAINT "Category_cover_image_id_fkey" FOREIGN KEY ("cover_image_id") REFERENCES "HostedImage"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Project" ADD CONSTRAINT "Project_thumbnail_image_id_fkey" FOREIGN KEY ("thumbnail_image_id") REFERENCES "HostedImage"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Project" ADD CONSTRAINT "Project_cover_image_id_fkey" FOREIGN KEY ("cover_image_id") REFERENCES "HostedImage"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Award" ADD CONSTRAINT "Award_image_id_fkey" FOREIGN KEY ("image_id") REFERENCES "HostedImage"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Story" ADD CONSTRAINT "Story_cover_image_id_fkey" FOREIGN KEY ("cover_image_id") REFERENCES "HostedImage"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Hackathon" ADD CONSTRAINT "Hackathon_cover_image_id_fkey" FOREIGN KEY ("cover_image_id") REFERENCES "HostedImage"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- A unique constraint covering the columns `[thumbnail_image_id]` on the table `Tournament` will be added. If there are existing duplicate values, this will fail.
|
||||
- A unique constraint covering the columns `[cover_image_id]` on the table `Tournament` will be added. If there are existing duplicate values, this will fail.
|
||||
- A unique constraint covering the columns `[image_id]` on the table `TournamentEvent` will be added. If there are existing duplicate values, this will fail.
|
||||
- A unique constraint covering the columns `[avatar_id]` on the table `TournamentJudge` will be added. If there are existing duplicate values, this will fail.
|
||||
- A unique constraint covering the columns `[image_id]` on the table `TournamentPrize` will be added. If there are existing duplicate values, this will fail.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "Tournament" ADD COLUMN "cover_image_id" INTEGER,
|
||||
ADD COLUMN "thumbnail_image_id" INTEGER;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "TournamentEvent" ADD COLUMN "image_id" INTEGER;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "TournamentJudge" ADD COLUMN "avatar_id" INTEGER;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "TournamentPrize" ADD COLUMN "image_id" INTEGER;
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Tournament_thumbnail_image_id_key" ON "Tournament"("thumbnail_image_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Tournament_cover_image_id_key" ON "Tournament"("cover_image_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "TournamentEvent_image_id_key" ON "TournamentEvent"("image_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "TournamentJudge_avatar_id_key" ON "TournamentJudge"("avatar_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "TournamentPrize_image_id_key" ON "TournamentPrize"("image_id");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Tournament" ADD CONSTRAINT "Tournament_thumbnail_image_id_fkey" FOREIGN KEY ("thumbnail_image_id") REFERENCES "HostedImage"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Tournament" ADD CONSTRAINT "Tournament_cover_image_id_fkey" FOREIGN KEY ("cover_image_id") REFERENCES "HostedImage"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "TournamentPrize" ADD CONSTRAINT "TournamentPrize_image_id_fkey" FOREIGN KEY ("image_id") REFERENCES "HostedImage"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "TournamentJudge" ADD CONSTRAINT "TournamentJudge_avatar_id_fkey" FOREIGN KEY ("avatar_id") REFERENCES "HostedImage"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "TournamentEvent" ADD CONSTRAINT "TournamentEvent_image_id_fkey" FOREIGN KEY ("image_id") REFERENCES "HostedImage"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
@@ -1,38 +0,0 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Project" ADD COLUMN "discord" TEXT NOT NULL DEFAULT E'',
|
||||
ADD COLUMN "github" TEXT NOT NULL DEFAULT E'',
|
||||
ADD COLUMN "hashtag" TEXT NOT NULL DEFAULT E'',
|
||||
ADD COLUMN "launch_status" TEXT NOT NULL DEFAULT E'',
|
||||
ADD COLUMN "tagline" TEXT NOT NULL DEFAULT E'',
|
||||
ADD COLUMN "twitter" TEXT NOT NULL DEFAULT E'';
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Capability" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"icon" TEXT,
|
||||
"is_official" BOOLEAN NOT NULL DEFAULT false,
|
||||
|
||||
CONSTRAINT "Capability_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "_CapabilityToProject" (
|
||||
"A" INTEGER NOT NULL,
|
||||
"B" INTEGER NOT NULL
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Capability_title_key" ON "Capability"("title");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "_CapabilityToProject_AB_unique" ON "_CapabilityToProject"("A", "B");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "_CapabilityToProject_B_index" ON "_CapabilityToProject"("B");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_CapabilityToProject" ADD FOREIGN KEY ("A") REFERENCES "Capability"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_CapabilityToProject" ADD FOREIGN KEY ("B") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -1,18 +0,0 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `is_official` on the `Capability` table. All the data in the column will be lost.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "Capability" DROP COLUMN "is_official";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Project" ADD COLUMN "slack" TEXT,
|
||||
ADD COLUMN "telegram" TEXT,
|
||||
ALTER COLUMN "discord" DROP NOT NULL,
|
||||
ALTER COLUMN "discord" DROP DEFAULT,
|
||||
ALTER COLUMN "github" DROP NOT NULL,
|
||||
ALTER COLUMN "github" DROP DEFAULT,
|
||||
ALTER COLUMN "twitter" DROP NOT NULL,
|
||||
ALTER COLUMN "twitter" DROP DEFAULT;
|
||||
@@ -1,14 +0,0 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "ProjectMember" (
|
||||
"projectId" INTEGER NOT NULL,
|
||||
"userId" INTEGER NOT NULL,
|
||||
"level" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "ProjectMember_pkey" PRIMARY KEY ("projectId","userId")
|
||||
);
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "ProjectMember" ADD CONSTRAINT "ProjectMember_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "ProjectMember" ADD CONSTRAINT "ProjectMember_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
@@ -1,10 +0,0 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `level` on the `ProjectMember` table. All the data in the column will be lost.
|
||||
- Added the required column `role` to the `ProjectMember` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "ProjectMember" DROP COLUMN "level",
|
||||
ADD COLUMN "role" TEXT NOT NULL;
|
||||
@@ -1,2 +0,0 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Project" ALTER COLUMN "launch_status" SET DEFAULT E'Launched';
|
||||
@@ -1,2 +0,0 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Project" ADD COLUMN "contact_email" TEXT;
|
||||
@@ -1,5 +0,0 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Story" ADD COLUMN "project_id" INTEGER;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Story" ADD CONSTRAINT "Story_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "Project"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
@@ -1,3 +0,0 @@
|
||||
# Please do not edit this file manually
|
||||
# It should be added in your version-control system (i.e. Git)
|
||||
provider = "postgresql"
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user