migrate to mongodb

This commit is contained in:
2023-11-26 21:11:58 -06:00
parent f05f2c59e8
commit 5c61014632
9 changed files with 91 additions and 105 deletions

View File

@@ -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,

View File

@@ -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",

View File

@@ -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;
}
}
}

View 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,
})
}
}

View File

@@ -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,
}
})
}

View File

@@ -4,5 +4,3 @@ export class PostgresError extends Error {
this.name = "PostgresError";
}
}
export { default as createDBClient } from "./createClient";

View File

@@ -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>;

View File

@@ -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)
);

View File

@@ -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);