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
|
## 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.
|
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
|
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]
|
[dev]
|
||||||
framework = "#static"
|
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
|
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",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -99,31 +99,14 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"client:dev-server": "env-cmd -f ./envs/client/dev-server.env react-scripts start",
|
"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",
|
"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",
|
"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": "react-scripts build",
|
||||||
"build:mocks": "env-cmd -f ./envs/client/mock-server.env 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",
|
"test": "react-scripts test",
|
||||||
"eject": "react-scripts eject",
|
"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"
|
"netlify:start": "set NETLIFY=true&& netlify dev"
|
||||||
},
|
},
|
||||||
"prisma": {
|
|
||||||
"seed": "node prisma/seed/index.js"
|
|
||||||
},
|
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"extends": [
|
"extends": [
|
||||||
"react-app",
|
"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