|
1 | 1 | import express from "express"; |
2 | 2 | import http from "http"; |
3 | | -import fs from "fs"; |
| 3 | +import { WebSocketServer } from "ws"; |
4 | 4 |
|
5 | | -import { ApolloServer, gql } from "apollo-server-express"; |
| 5 | +import { createSetupWSConnection } from "@codepod/yjs"; |
| 6 | +import { bindState, writeState } from "./yjs-blob"; |
6 | 7 |
|
7 | | -import { ApolloServerPluginLandingPageLocalDefault } from "apollo-server-core"; |
8 | | - |
9 | | -import { customAlphabet } from "nanoid/async"; |
10 | | -import { lowercase, numbers } from "nanoid-dictionary"; |
11 | | - |
12 | | -const nanoid = customAlphabet(lowercase + numbers, 20); |
13 | | - |
14 | | -export const typeDefs = gql` |
15 | | - type User { |
16 | | - id: ID! |
17 | | - username: String |
18 | | - email: String! |
19 | | - password: String! |
20 | | - firstname: String! |
21 | | - lastname: String! |
22 | | - } |
23 | | -
|
24 | | - type Repo { |
25 | | - id: ID! |
26 | | - name: String |
27 | | - userId: ID! |
28 | | - stargazers: [User] |
29 | | - collaborators: [User] |
30 | | - public: Boolean |
31 | | - createdAt: String |
32 | | - updatedAt: String |
33 | | - accessedAt: String |
34 | | - } |
35 | | -
|
36 | | - type Query { |
37 | | - hello: String |
38 | | - me: User |
39 | | - repo(id: String!): Repo |
40 | | - getDashboardRepos: [Repo] |
41 | | - } |
42 | | -
|
43 | | - type Mutation { |
44 | | - world: String |
45 | | - createRepo: Repo |
46 | | - updateRepo(id: ID!, name: String!): Boolean |
47 | | - deleteRepo(id: ID!): Boolean |
48 | | - star(repoId: ID!): Boolean |
49 | | - unstar(repoId: ID!): Boolean |
50 | | - } |
51 | | -`; |
52 | | - |
53 | | -const CODEPOD_ROOT = "/var/codepod"; |
54 | | -const repoDirs = `${CODEPOD_ROOT}/repos`; |
55 | | - |
56 | | -export const resolvers = { |
57 | | - Query: { |
58 | | - hello: () => { |
59 | | - return "Hello world!"; |
60 | | - }, |
61 | | - // TODO Dummy Data |
62 | | - me: () => { |
63 | | - return { |
64 | | - id: "localUser", |
65 | | - username: "localUser", |
66 | | - email: "", |
67 | | - firstname: "Local", |
68 | | - lastname: "User", |
69 | | - }; |
70 | | - }, |
71 | | - getDashboardRepos: () => { |
72 | | - if (!fs.existsSync(repoDirs)) fs.mkdirSync(repoDirs); |
73 | | - // list folders in repoDirs |
74 | | - const dirs = fs.readdirSync(repoDirs); |
75 | | - // read meta data from dirs |
76 | | - const repos = dirs.map((dir) => { |
77 | | - const meta = JSON.parse( |
78 | | - fs.readFileSync(`${repoDirs}/${dir}/meta.json`, "utf8") |
79 | | - ); |
80 | | - return { |
81 | | - id: meta.id, |
82 | | - name: meta.name, |
83 | | - userId: meta.userId, |
84 | | - stargazers: (meta.stargazers || []).map((id) => ({ id })), |
85 | | - collaborators: [], |
86 | | - public: meta.public, |
87 | | - createdAt: meta.createdAt, |
88 | | - updatedAt: meta.updatedAt, |
89 | | - accessedAt: meta.accessedAt, |
90 | | - }; |
91 | | - }); |
92 | | - return repos; |
93 | | - }, |
94 | | - repo: (_, { id }) => { |
95 | | - // read meta data from dirs |
96 | | - const meta = JSON.parse( |
97 | | - fs.readFileSync(`${repoDirs}/${id}/meta.json`, "utf8") |
98 | | - ); |
99 | | - // update the accessedAt time |
100 | | - meta.accessedAt = new Date().getTime(); |
101 | | - fs.writeFileSync(`${repoDirs}/${id}/meta.json`, JSON.stringify(meta)); |
102 | | - return { |
103 | | - id: meta.id, |
104 | | - name: meta.name, |
105 | | - userId: meta.userId, |
106 | | - stargazers: (meta.stargazers || []).map((id) => ({ id })), |
107 | | - collaborators: [], |
108 | | - public: meta.public, |
109 | | - createdAt: meta.createdAt, |
110 | | - updatedAt: meta.updatedAt, |
111 | | - accessedAt: meta.accessedAt, |
112 | | - }; |
113 | | - }, |
114 | | - }, |
115 | | - Mutation: { |
116 | | - world: () => { |
117 | | - return "World!"; |
118 | | - }, |
119 | | - // TODO fill APIs |
120 | | - createRepo: async () => { |
121 | | - const id = await nanoid(); |
122 | | - // Integer representing the number of milliseconds that have elapsed since the Unix epoch. |
123 | | - const time = new Date().getTime(); |
124 | | - const meta = { |
125 | | - id, |
126 | | - name: null, |
127 | | - userId: "localUser", |
128 | | - public: false, |
129 | | - stargazers: [], |
130 | | - collaborators: [], |
131 | | - createdAt: time, |
132 | | - updatedAt: time, |
133 | | - accessedAt: time, |
134 | | - }; |
135 | | - if (!fs.existsSync(repoDirs)) fs.mkdirSync(repoDirs); |
136 | | - fs.mkdirSync(`${repoDirs}/${id}`); |
137 | | - fs.writeFileSync(`${repoDirs}/${id}/meta.json`, JSON.stringify(meta)); |
138 | | - return meta; |
139 | | - }, |
140 | | - updateRepo: (_, { id, name }) => { |
141 | | - const meta = JSON.parse( |
142 | | - fs.readFileSync(`${repoDirs}/${id}/meta.json`, "utf8") |
143 | | - ); |
144 | | - meta.name = name; |
145 | | - fs.writeFileSync(`${repoDirs}/${id}/meta.json`, JSON.stringify(meta)); |
146 | | - return true; |
147 | | - }, |
148 | | - deleteRepo: (_, { id }) => { |
149 | | - fs.rmdirSync(`${repoDirs}/${id}`, { recursive: true }); |
150 | | - return true; |
151 | | - }, |
152 | | - star: (_, { repoId }) => { |
153 | | - const meta = JSON.parse( |
154 | | - fs.readFileSync(`${repoDirs}/${repoId}/meta.json`, "utf8") |
155 | | - ); |
156 | | - meta.stargazers = ["localUser"]; |
157 | | - fs.writeFileSync(`${repoDirs}/${repoId}/meta.json`, JSON.stringify(meta)); |
158 | | - return true; |
159 | | - }, |
160 | | - unstar: (_, { repoId }) => { |
161 | | - const meta = JSON.parse( |
162 | | - fs.readFileSync(`${repoDirs}/${repoId}/meta.json`, "utf8") |
163 | | - ); |
164 | | - meta.stargazers = []; |
165 | | - fs.writeFileSync(`${repoDirs}/${repoId}/meta.json`, JSON.stringify(meta)); |
166 | | - return true; |
167 | | - }, |
168 | | - }, |
169 | | -}; |
| 8 | +import cors from "cors"; |
170 | 9 |
|
171 | 10 | export async function startServer({ port }) { |
172 | | - const apollo = new ApolloServer({ |
173 | | - typeDefs, |
174 | | - resolvers, |
175 | | - plugins: [ApolloServerPluginLandingPageLocalDefault({ embed: true })], |
| 11 | + const app = express(); |
| 12 | + app.use(express.json({ limit: "20mb" })); |
| 13 | + // support cors |
| 14 | + app.use(cors()); |
| 15 | + // serve static files generated from UI |
| 16 | + app.use(express.static("../../packages/ui/dist")); |
| 17 | + |
| 18 | + const http_server = http.createServer(app); |
| 19 | + |
| 20 | + // Yjs websocket |
| 21 | + const wss = new WebSocketServer({ noServer: true }); |
| 22 | + |
| 23 | + wss.on("connection", (...args) => |
| 24 | + createSetupWSConnection(bindState, writeState)(...args) |
| 25 | + ); |
| 26 | + |
| 27 | + http_server.on("upgrade", async (request, socket, head) => { |
| 28 | + // You may check auth of request here.. |
| 29 | + // See https://github.com/websockets/ws#client-authentication |
| 30 | + if (request.url) { |
| 31 | + wss.handleUpgrade(request, socket, head, function done(ws) { |
| 32 | + wss.emit("connection", ws, request, { readOnly: false }); |
| 33 | + }); |
| 34 | + return; |
| 35 | + } |
| 36 | + socket.write("HTTP/1.1 401 Unauthorized\r\n\r\n"); |
| 37 | + socket.destroy(); |
| 38 | + return; |
176 | 39 | }); |
177 | | - const expapp = express(); |
178 | | - expapp.use(express.json({ limit: "20mb" })); |
179 | | - const http_server = http.createServer(expapp); |
180 | | - |
181 | | - await apollo.start(); |
182 | | - apollo.applyMiddleware({ app: expapp }); |
183 | 40 |
|
184 | 41 | http_server.listen({ port }, () => { |
185 | 42 | console.log(`🚀 Server ready at http://localhost:${port}`); |
|
0 commit comments