migrate to mongodb
This commit is contained in:
8
env.mjs
8
env.mjs
@@ -5,6 +5,10 @@ const env = createEnv({
|
||||
server: {
|
||||
NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
|
||||
|
||||
MONGO_URL: z.string().url(),
|
||||
MONGO_USER: z.string(),
|
||||
MONGO_PASSWORD: z.string(),
|
||||
|
||||
POSTGRES_URL: z.string().url(),
|
||||
POSTGRES_USER: z.string(),
|
||||
POSTGRES_PASSWORD: z.string(),
|
||||
@@ -22,6 +26,10 @@ const env = createEnv({
|
||||
POSTGRES_USER: process.env.POSTGRES_USER,
|
||||
POSTGRES_PASSWORD: process.env.POSTGRES_PASSWORD,
|
||||
|
||||
MONGO_URL: process.env.MONGO_URL,
|
||||
MONGO_USER: process.env.MONGO_USER,
|
||||
MONGO_PASSWORD: process.env.MONGO_PASSWORD,
|
||||
|
||||
S3_ENDPOINT: process.env.S3_ENDPOINT,
|
||||
S3_REGION: process.env.S3_REGION,
|
||||
S3_ACCESS_KEY: process.env.S3_ACCESS_KEY,
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
"@vercel/style-guide": "^5.0.1",
|
||||
"autoprefixer": "10.4.14",
|
||||
"ioredis": "^5.3.2",
|
||||
"mongodb": "^6.3.0",
|
||||
"next": "^14.0.1",
|
||||
"pg": "^8.11.3",
|
||||
"react": "18.2.0",
|
||||
@@ -26,7 +27,7 @@
|
||||
"uuid": "^9.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "20.2.5",
|
||||
"@types/node": "^20.10.0",
|
||||
"@types/pg": "^8.10.3",
|
||||
"@types/react": "18.2.7",
|
||||
"@types/react-dom": "18.2.4",
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { S3Client } from '@aws-sdk/client-s3';
|
||||
// import Redis from 'ioredis';
|
||||
import pg from 'pg';
|
||||
import { createDBClient } from '../db';
|
||||
import { createDBClient } from '../db/createClient';
|
||||
import { Maybe, must } from '@/util/helpers';
|
||||
import { createS3Client } from '../s3';
|
||||
import createRedisClient from '../cache/createClient';
|
||||
import { ParseParams, SafeParseReturnType } from 'zod';
|
||||
import { MongoClient, WithId, Filter, InsertOneResult } from 'mongodb';
|
||||
|
||||
type FullParserType<T extends { [key: string]: any }> = (data: any, params?: Partial<ParseParams> | undefined) => SafeParseReturnType<any, T>
|
||||
|
||||
@@ -14,67 +15,68 @@ type ControllerOptions<T extends { [key: string]: any }> = {
|
||||
parser?: FullParserType<T>
|
||||
}
|
||||
|
||||
export default abstract class BaseController<T extends { [key: string]: any }> {
|
||||
protected db: pg.Client
|
||||
// #bucket: S3Client
|
||||
// #cache: Redis
|
||||
|
||||
tableName: string
|
||||
export default abstract class BaseController<T extends { _id?: any, [key: string]: any }> {
|
||||
protected client: MongoClient
|
||||
collectionName: string
|
||||
parser?: FullParserType<T>
|
||||
|
||||
constructor(options: ControllerOptions<T>) {
|
||||
this.db = must(createDBClient);
|
||||
// this.#bucket = must(createS3Client);
|
||||
// this.#cache = must(createRedisClient);
|
||||
|
||||
this.tableName = options.tableName;
|
||||
this.parser = options.parser;
|
||||
this.collectionName = options.tableName;
|
||||
this.client = createDBClient();
|
||||
this.parser = options.parser;
|
||||
}
|
||||
|
||||
async getAll(): Promise<Maybe<T[]>> {
|
||||
async getAll() {
|
||||
'use server';
|
||||
|
||||
let result: Maybe<WithId<T>[]>;
|
||||
|
||||
try {
|
||||
// we'll enable cache here later
|
||||
await this.db.connect();
|
||||
const result = await this.db.query(`SELECT * FROM ${this.tableName}`);
|
||||
await this.client.connect();
|
||||
result = await this.client.db().collection<T>(this.collectionName)
|
||||
.find()
|
||||
.toArray();
|
||||
|
||||
if (this.parser) {
|
||||
result.rows.forEach((row, idx) => {
|
||||
const parsed = (this.parser as FullParserType<T>)(row);
|
||||
if (!parsed.success) {
|
||||
console.log(`Failed to parse row ${idx} of ${this.tableName}`);
|
||||
console.log(parsed.error);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return result.rows;
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.log({ error });
|
||||
return null;
|
||||
result = null;
|
||||
} finally {
|
||||
await this.db.end();
|
||||
await this.client.close();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
async getByID(id: number, projection?: (keyof T)[]): Promise<Maybe<T>> {
|
||||
async getByID(id: number): Promise<Maybe<WithId<T>>> {
|
||||
let result: Maybe<WithId<T>>;
|
||||
|
||||
try {
|
||||
await this.db.connect();
|
||||
const finalProjection = projection?.join(", ") ?? "*";
|
||||
|
||||
const result = await this.db.query(`SELECT ${finalProjection} FROM ${this.tableName} WHERE id = ${id}`);
|
||||
|
||||
if (this.parser) {
|
||||
const parsed = this.parser(result.rows[0]);
|
||||
if (parsed.success) return parsed.data;
|
||||
}
|
||||
|
||||
return result.rows[0];
|
||||
await this.client.connect();
|
||||
result = await this.client.db().collection<T>(this.collectionName)
|
||||
.findOne({ where: { _id: id }});
|
||||
} catch (error) {
|
||||
console.log({ error });
|
||||
return null;
|
||||
result = null;
|
||||
} finally {
|
||||
await this.db.end();
|
||||
await this.client.close();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
async post(data: T) {
|
||||
let result: Maybe<InsertOneResult<T>>;
|
||||
|
||||
try {
|
||||
await this.client.connect();
|
||||
result = await this.client.db().collection<T>(this.collectionName)
|
||||
.insertOne(data as any);
|
||||
} catch(error) {
|
||||
console.log({ error });
|
||||
result = null;
|
||||
} finally {
|
||||
await this.client.close();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
11
server/controllers/blogpost.controller.ts
Normal file
11
server/controllers/blogpost.controller.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { ZBlogPost } from "../db/schema";
|
||||
import BaseController from "./base.controller";
|
||||
|
||||
export default class BlogPostController extends BaseController<any> {
|
||||
constructor() {
|
||||
super({
|
||||
tableName: "blogposts",
|
||||
parser: ZBlogPost.safeParse,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,11 @@
|
||||
import { env } from "@/env.mjs";
|
||||
import { Client } from "pg";
|
||||
import { MongoClient } from "mongodb";
|
||||
|
||||
export default function createDBClient() {
|
||||
try {
|
||||
return new Client({
|
||||
connectionString: `${env.POSTGRES_URL}?sslmode=require`,
|
||||
// user: env.POSTGRES_USER,
|
||||
// password: env.POSTGRES_PASSWORD,
|
||||
});
|
||||
} catch(e) {
|
||||
console.log('error creating client', e);
|
||||
return null;
|
||||
}
|
||||
export function createDBClient() {
|
||||
return new MongoClient(env.MONGO_URL, {
|
||||
auth: {
|
||||
username: env.MONGO_USER,
|
||||
password: env.MONGO_PASSWORD,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,5 +4,3 @@ export class PostgresError extends Error {
|
||||
this.name = "PostgresError";
|
||||
}
|
||||
}
|
||||
|
||||
export { default as createDBClient } from "./createClient";
|
||||
|
||||
@@ -4,7 +4,7 @@ const filePathMatcher = /^[1-9]{1,3}\.(wav|mp3|(jpe?g)|png)$/;
|
||||
const ZFileName = z.string().regex(filePathMatcher);
|
||||
|
||||
export const ZMusicStreamingEntry = z.object({
|
||||
id: z.number(),
|
||||
_id: z.any(),
|
||||
name: z.string().max(100),
|
||||
shortdescription: z.string().max(100),
|
||||
longdescription: z.string().max(1000),
|
||||
@@ -18,5 +18,21 @@ export const ZMusicStreamingEntry = z.object({
|
||||
tags: z.array(z.string().max(100)).optional(),
|
||||
});
|
||||
|
||||
export const ZBlogPost = z.object({
|
||||
_id: z.any(),
|
||||
title: z.string().max(100),
|
||||
author: z.string().max(100),
|
||||
content: z.string(),
|
||||
|
||||
images: z.array(z.string()).optional(),
|
||||
|
||||
posted: z.date(),
|
||||
written: z.date().optional(),
|
||||
updated: z.date().optional(),
|
||||
|
||||
tags: z.array(z.string().max(100)).optional(),
|
||||
})
|
||||
|
||||
export type MusicStreamingEntry = z.infer<typeof ZMusicStreamingEntry>;
|
||||
export type BlogPost = z.infer<typeof ZBlogPost>;
|
||||
export type ValidFileName = z.infer<typeof ZFileName>;
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
CREATE TABLE IF NOT EXISTS music (
|
||||
id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
name varchar(100),
|
||||
shortDescription varchar(100),
|
||||
longDescription varchar(1000),
|
||||
pathToEntry varchar
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tags (
|
||||
id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
name varchar(100)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS music_tags (
|
||||
id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
music_id INTEGER,
|
||||
tag_id INTEGER,
|
||||
FOREIGN KEY(music_id) REFERENCES music(id),
|
||||
FOREIGN KEY(tag_id) REFERENCES tags(id)
|
||||
);
|
||||
@@ -1,26 +0,0 @@
|
||||
INSERT INTO music (name, shortDescription, longDescription, pathToEntry) VALUES (
|
||||
'Jisei',
|
||||
'My first major undertaking as a singer-songwriter',
|
||||
'Released in October 2020',
|
||||
'Jisei'
|
||||
);
|
||||
|
||||
INSERT INTO tags (name) VALUES
|
||||
(
|
||||
'indie-rock'
|
||||
),
|
||||
(
|
||||
'alternative'
|
||||
),
|
||||
(
|
||||
'guitar'
|
||||
),
|
||||
(
|
||||
'cello'
|
||||
);
|
||||
|
||||
INSERT INTO music_tags (music_id, tag_id) VALUES
|
||||
(1, 1),
|
||||
(1, 2),
|
||||
(1, 3),
|
||||
(1, 4);
|
||||
Reference in New Issue
Block a user