From c1ce833269c755397201560577b9598ac02b3c9b Mon Sep 17 00:00:00 2001 From: "@wwwjim" Date: Thu, 3 Aug 2023 16:42:55 -0700 Subject: [PATCH] 1.0.0 --- .gitignore | 20 +++ .prettierrc | 23 +++ README.md | 43 +++++ app/head.tsx | 29 +++ app/layout.tsx | 7 + app/page.tsx | 7 + common/http.ts | 18 ++ common/server.ts | 19 ++ common/utilities.ts | 168 ++++++++++++++++++ components/DefaultLayout.module.scss | 3 + components/DefaultLayout.tsx | 7 + components/DefaultMetaTags.tsx | 11 ++ data/db.ts | 21 +++ data/query.ts | 37 ++++ global.scss | 118 +++++++++++++ modules/cors.ts | 252 +++++++++++++++++++++++++++ modules/object-assign.ts | 97 +++++++++++ modules/vary.ts | 147 ++++++++++++++++ next-env.d.ts | 6 + next.config.js | 10 ++ package.json | 29 +++ pages/api/index.ts | 9 + public/favicon-16x16.png | Bin 0 -> 201 bytes public/favicon-32x32.png | Bin 0 -> 388 bytes public/favicon.ico | Bin 0 -> 15086 bytes scripts/example.js | 33 ++++ scripts/index.js | 1 + tsconfig.json | 59 +++++++ 28 files changed, 1174 insertions(+) create mode 100644 .gitignore create mode 100644 .prettierrc create mode 100644 README.md create mode 100644 app/head.tsx create mode 100644 app/layout.tsx create mode 100644 app/page.tsx create mode 100644 common/http.ts create mode 100644 common/server.ts create mode 100644 common/utilities.ts create mode 100644 components/DefaultLayout.module.scss create mode 100644 components/DefaultLayout.tsx create mode 100644 components/DefaultMetaTags.tsx create mode 100644 data/db.ts create mode 100644 data/query.ts create mode 100644 global.scss create mode 100644 modules/cors.ts create mode 100644 modules/object-assign.ts create mode 100644 modules/vary.ts create mode 100644 next-env.d.ts create mode 100644 next.config.js create mode 100644 package.json create mode 100644 pages/api/index.ts create mode 100644 public/favicon-16x16.png create mode 100644 public/favicon-32x32.png create mode 100644 public/favicon.ico create mode 100644 scripts/example.js create mode 100644 scripts/index.js create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bf65c2d --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +.next +.nova +.env +.env.local +.env-custom-development +.env-development +.env-textile +.env-production +.DS_STORE +DS_STORE +yarn.lock +node_modules +dist +analytics.txt +package-lock.json + +/**/*/.DS_STORE +/**/*/node_modules +/**/*/.next +/**/*/.data \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..e44d5e1 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,23 @@ +{ + "printWidth":180, + "tabWidth":2, + "useTabs":false, + "semi":true, + "singleQuote":true, + "trailingComma":"es5", + "bracketSpacing":true, + "jsxBracketSameLine":false, + "arrowParens":"always", + "requirePragma":false, + "insertPragma":false, + "proseWrap":"preserve", + "parser":"babel", + "overrides": [ + { + "files": "*.js", + "options": { + "parser": "babel" + } + } + ] +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..bfc2695 --- /dev/null +++ b/README.md @@ -0,0 +1,43 @@ +# NEXT-SASS-STARTER + +Why would I use this? + +- Quickly start a project with TypeScript, SASS, and NextJS. +- You want to make a website quickly. + +### Setup (MacOS) + +Start by cloning the repository, or by clicking on **Use this template** above. + +Then run the server + +```sh +npm install +npm run dev +``` + +Go to `http://localhost:3005` in your browser of choice. Enjoy! + +### Scripts + +If you need to run node script without running the server, use this example to get started + +```sh +npm run script example +``` + +### Env Variables + +If you want to connect to a Postgres database, something I do all the time, provide the following `.env` file. `5432` is the default Postgres port. + +```sh +DOCUMENT_DATABASE_NAME=xxxx +DOCUMENT_DATABASE_USERNAME=xxxx +DOCUMENT_DATABASE_HOST=xxxx +DOCUMENT_DATABASE_PORT=5432 +DOCUMENT_DATABASE_PASSWORD=xxxx +``` + +### Contact + +If you have questions ping me on Twitter, [@wwwjim](https://www.twitter.com/wwwjim). diff --git a/app/head.tsx b/app/head.tsx new file mode 100644 index 0000000..e83b364 --- /dev/null +++ b/app/head.tsx @@ -0,0 +1,29 @@ +import DefaultMetaTags from '@components/DefaultMetaTags'; + +export default async function Head({ params }) { + const title = 'example'; + const description = 'CHANGEME: description for your application using next-sass'; + const url = 'CHANGEME: your-production-url.tld'; + + // SUMMARY_LARGE_IMAGE: 1500x785 + return ( + <> + {title} + + + + + + + + + + + + + + + + + ); +} diff --git a/app/layout.tsx b/app/layout.tsx new file mode 100644 index 0000000..f3ef34c --- /dev/null +++ b/app/layout.tsx @@ -0,0 +1,7 @@ +export default function RootLayout({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ); +} diff --git a/app/page.tsx b/app/page.tsx new file mode 100644 index 0000000..f3e8141 --- /dev/null +++ b/app/page.tsx @@ -0,0 +1,7 @@ +import '@root/global.scss'; + +import DefaultLayout from '@components/DefaultLayout'; + +export default async function Page(props) { + return Hello World; +} diff --git a/common/http.ts b/common/http.ts new file mode 100644 index 0000000..b972149 --- /dev/null +++ b/common/http.ts @@ -0,0 +1,18 @@ +const REQUEST_HEADERS = { + Accept: 'application/json', + 'Content-Type': 'application/json', +}; + +const getHeaders = (key) => { + return { ...REQUEST_HEADERS, Authorization: `Bearer ${key}` }; +}; + +export async function placeholder(key) { + const response = await fetch('/api', { + method: 'GET', + headers: getHeaders(key), + }); + + const json = await response.json(); + return json; +} diff --git a/common/server.ts b/common/server.ts new file mode 100644 index 0000000..608be7d --- /dev/null +++ b/common/server.ts @@ -0,0 +1,19 @@ +import Cors from '@modules/cors'; + +export function initMiddleware(middleware) { + return (req, res) => + new Promise((resolve, reject) => { + middleware(req, res, (result) => { + if (result instanceof Error) { + return reject(result); + } + return resolve(result); + }); + }); +} + +export const cors = initMiddleware( + Cors({ + methods: ['GET', 'POST', 'OPTIONS'], + }) +); diff --git a/common/utilities.ts b/common/utilities.ts new file mode 100644 index 0000000..0a2ef99 --- /dev/null +++ b/common/utilities.ts @@ -0,0 +1,168 @@ +const hasOwn = {}.hasOwnProperty; +const protocolAndDomainRE = /^(?:\w+:)?\/\/(\S+)$/; +const localhostDomainRE = /^localhost[\:?\d]*(?:[^\:?\d]\S*)?$/; +const nonLocalhostDomainRE = /^[^\s\.]+\.\S{2,}$/; + +export const noop = () => {}; + +export const pluralize = (text, count) => { + return count > 1 || count === 0 ? `${text}s` : text; +}; + +export function toDateISOString(data: string) { + const date = new Date(data); + return date.toLocaleDateString('en-US', { + weekday: 'long', + day: 'numeric', + month: 'long', + year: 'numeric', + }); +} + +export const elide = (string, length = 140, emptyState = '...') => { + if (isEmpty(string)) { + return emptyState; + } + + if (string.length < length) { + return string.trim(); + } + + return `${string.substring(0, length)}...`; +}; + +export function bytesToSize(bytes: number, decimals: number = 2) { + if (bytes === 0) return '0 Bytes'; + + const k = 1000; + const dm = decimals < 0 ? 0 : decimals; + const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + + const i = Math.floor(Math.log(bytes) / Math.log(k)); + + return `${(bytes / Math.pow(k, i)).toFixed(dm)} ${sizes[i]}`; +} + +export function isEmpty(text: any) { + // NOTE(jim): If a number gets passed in, it isn't considered empty for zero. + if (text === 0) { + return false; + } + + if (!text) { + return true; + } + + if (typeof text === 'object') { + return true; + } + + if (text.length === 0) { + return true; + } + + text = text.toString(); + + return Boolean(!text.trim()); +} + +export function createSlug(text: any) { + if (isEmpty(text)) { + return 'untitled'; + } + + const a = 'æøåàáäâèéëêìíïîòóöôùúüûñçßÿœæŕśńṕẃǵǹḿǘẍźḧ·/_,:;'; + const b = 'aoaaaaaeeeeiiiioooouuuuncsyoarsnpwgnmuxzh------'; + const p = new RegExp(a.split('').join('|'), 'g'); + + return text + .toString() + .toLowerCase() + .replace(/\s+/g, '-') // Replace spaces with - + .replace(p, (c) => b.charAt(a.indexOf(c))) // Replace special chars + .replace(/&/g, '-and-') // Replace & with 'and' + .replace(/[^\w\-]+/g, '') // Remove all non-word chars + .replace(/\-\-+/g, '-') // Replace multiple - with single - + .replace(/^-+/, '') // Trim - from start of text + .replace(/-+$/, ''); // Trim - from end of text +} + +export function isUrl(string: any) { + if (typeof string !== 'string') { + return false; + } + + var match = string.match(protocolAndDomainRE); + if (!match) { + return false; + } + + var everythingAfterProtocol = match[1]; + if (!everythingAfterProtocol) { + return false; + } + + if (localhostDomainRE.test(everythingAfterProtocol) || nonLocalhostDomainRE.test(everythingAfterProtocol)) { + return true; + } + + return false; +} + +export function debounce(fn: (...args: Args) => void, delay: number) { + let timeoutID: number | undefined; + let lastArgs: Args | undefined; + + const run = () => { + if (lastArgs) { + fn(...lastArgs); + lastArgs = undefined; + } + }; + + const debounced = (...args: Args) => { + clearTimeout(timeoutID); + lastArgs = args; + timeoutID = window.setTimeout(run, delay); + }; + + debounced.flush = () => { + clearTimeout(timeoutID); + }; + + return debounced; +} + +export function classNames(...args: any[]): string { + var classes = []; + + for (var i = 0; i < arguments.length; i++) { + var arg = arguments[i]; + if (!arg) continue; + + var argType = typeof arg; + + if (argType === 'string' || argType === 'number') { + classes.push(arg); + } else if (Array.isArray(arg)) { + if (arg.length) { + var inner = classNames.apply(null, arg); + if (inner) { + classes.push(inner); + } + } + } else if (argType === 'object') { + if (arg.toString !== Object.prototype.toString) { + classes.push(arg.toString()); + } else { + for (var key in arg) { + if (hasOwn.call(arg, key) && arg[key]) { + classes.push(key); + } + } + } + } + } + + return classes.join(' '); +} diff --git a/components/DefaultLayout.module.scss b/components/DefaultLayout.module.scss new file mode 100644 index 0000000..b4197b1 --- /dev/null +++ b/components/DefaultLayout.module.scss @@ -0,0 +1,3 @@ +.body { + color: red; +} diff --git a/components/DefaultLayout.tsx b/components/DefaultLayout.tsx new file mode 100644 index 0000000..51e6b02 --- /dev/null +++ b/components/DefaultLayout.tsx @@ -0,0 +1,7 @@ +import styles from '@components/DefaultLayout.module.scss'; + +import * as React from 'react'; + +export default function App(props) { + return
{props.children}
; +} diff --git a/components/DefaultMetaTags.tsx b/components/DefaultMetaTags.tsx new file mode 100644 index 0000000..b80920e --- /dev/null +++ b/components/DefaultMetaTags.tsx @@ -0,0 +1,11 @@ +export default function DefaultMetaTags() { + return ( + <> + + + + + + + ); +} diff --git a/data/db.ts b/data/db.ts new file mode 100644 index 0000000..a1f5a94 --- /dev/null +++ b/data/db.ts @@ -0,0 +1,21 @@ +if (process.env.NODE_ENV !== 'production') { + require('dotenv').config(); +} + +import knex from 'knex'; + +const ssl = process.env.EXAMPLE_DATABASE_HOST === '127.0.0.1' ? false : true; + +const DB = knex({ + client: 'pg', + connection: { + ssl: ssl, + port: Number(process.env.EXAMPLE_DATABASE_PORT), + host: process.env.EXAMPLE_DATABASE_HOST, + database: process.env.EXAMPLE_DATABASE_NAME, + user: process.env.EXAMPLE_DATABASE_USERNAME, + password: process.env.EXAMPLE_DATABASE_PASSWORD, + }, +}); + +export default DB; diff --git a/data/query.ts b/data/query.ts new file mode 100644 index 0000000..19d2ab9 --- /dev/null +++ b/data/query.ts @@ -0,0 +1,37 @@ +import DB from '@data/db'; + +function print({ address, copy }) { + console.log(`\x1b[1m\x1b[37m\[${address}\]\x1b[0m : ${copy}`); +} + +export const runQuery = async ({ queryFn, errorFn, label }) => { + let response; + try { + response = await queryFn(); + } catch (e) { + response = errorFn(e); + } + + print({ address: `database-query`, copy: label }); + return response; +}; + +export const example = async (options) => { + return await runQuery({ + label: 'EXAMPLE', + queryFn: async () => { + const query: any = await DB.insert(options) + .into('EXAMPLE_CHANGE_ME') + .returning('*'); + + const index = query ? query.pop() : null; + return index; + }, + errorFn: async (e) => { + return { + error: 'EXAMPLE', + source: e, + }; + }, + }); +}; diff --git a/global.scss b/global.scss new file mode 100644 index 0000000..b0a2ee9 --- /dev/null +++ b/global.scss @@ -0,0 +1,118 @@ +html, +body, +div, +span, +applet, +object, +iframe, +h1, +h2, +h3, +h4, +h5, +h6, +p, +blockquote, +pre, +a, +abbr, +acronym, +address, +big, +cite, +code, +del, +dfn, +em, +img, +ins, +kbd, +q, +s, +samp, +small, +strike, +strong, +sub, +sup, +tt, +var, +b, +u, +i, +center, +dl, +dt, +dd, +ol, +ul, +li, +fieldset, +form, +label, +legend, +table, +caption, +tbody, +tfoot, +thead, +tr, +th, +td, +article, +aside, +canvas, +details, +embed, +figure, +figcaption, +footer, +header, +hgroup, +menu, +nav, +output, +ruby, +section, +summary, +time, +mark, +audio, +video { + box-sizing: border-box; + vertical-align: baseline; + margin: 0; + padding: 0; + border: 0; +} + +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +menu, +nav, +section { + display: block; +} + +html, +body { + --color-background: #ffffff; + --color-text: #000000; + + font-size: 16px; + background: var(--color-background); + color: var(--color-text); + font-family: -apple-system, BlinkMacSystemFont, helvetica neue, helvetica, sans-serif; + scrollbar-width: none; + -ms-overflow-style: -ms-autohiding-scrollbar; + + ::-webkit-scrollbar { + display: none; + } +} diff --git a/modules/cors.ts b/modules/cors.ts new file mode 100644 index 0000000..db8c67d --- /dev/null +++ b/modules/cors.ts @@ -0,0 +1,252 @@ +// @ts-nocheck + +'use strict'; + +import assign from '@modules/object-assign'; +import vary from '@modules/vary'; + +var defaults = { + origin: '*', + methods: 'GET,HEAD,PUT,PATCH,POST,DELETE', + preflightContinue: false, + optionsSuccessStatus: 204, +}; + +function isString(s) { + return typeof s === 'string' || s instanceof String; +} + +function isOriginAllowed(origin, allowedOrigin) { + if (Array.isArray(allowedOrigin)) { + for (var i = 0; i < allowedOrigin.length; ++i) { + if (isOriginAllowed(origin, allowedOrigin[i])) { + return true; + } + } + return false; + } else if (isString(allowedOrigin)) { + return origin === allowedOrigin; + } else if (allowedOrigin instanceof RegExp) { + return allowedOrigin.test(origin); + } else { + return !!allowedOrigin; + } +} + +function configureOrigin(options, req) { + var requestOrigin = req.headers.origin, + headers = [], + isAllowed; + + if (!options.origin || options.origin === '*') { + // allow any origin + headers.push([ + { + key: 'Access-Control-Allow-Origin', + value: '*', + }, + ]); + } else if (isString(options.origin)) { + // fixed origin + headers.push([ + { + key: 'Access-Control-Allow-Origin', + value: options.origin, + }, + ]); + headers.push([ + { + key: 'Vary', + value: 'Origin', + }, + ]); + } else { + isAllowed = isOriginAllowed(requestOrigin, options.origin); + // reflect origin + headers.push([ + { + key: 'Access-Control-Allow-Origin', + value: isAllowed ? requestOrigin : false, + }, + ]); + headers.push([ + { + key: 'Vary', + value: 'Origin', + }, + ]); + } + + return headers; +} + +function configureMethods(options) { + var methods = options.methods; + if (methods.join) { + methods = options.methods.join(','); // .methods is an array, so turn it into a string + } + return { + key: 'Access-Control-Allow-Methods', + value: methods, + }; +} + +function configureCredentials(options) { + if (options.credentials === true) { + return { + key: 'Access-Control-Allow-Credentials', + value: 'true', + }; + } + return null; +} + +function configureAllowedHeaders(options, req) { + var allowedHeaders = options.allowedHeaders || options.headers; + var headers = []; + + if (!allowedHeaders) { + allowedHeaders = req.headers['access-control-request-headers']; // .headers wasn't specified, so reflect the request headers + headers.push([ + { + key: 'Vary', + value: 'Access-Control-Request-Headers', + }, + ]); + } else if (allowedHeaders.join) { + allowedHeaders = allowedHeaders.join(','); // .headers is an array, so turn it into a string + } + if (allowedHeaders && allowedHeaders.length) { + headers.push([ + { + key: 'Access-Control-Allow-Headers', + value: allowedHeaders, + }, + ]); + } + + return headers; +} + +function configureExposedHeaders(options) { + var headers = options.exposedHeaders; + if (!headers) { + return null; + } else if (headers.join) { + headers = headers.join(','); // .headers is an array, so turn it into a string + } + if (headers && headers.length) { + return { + key: 'Access-Control-Expose-Headers', + value: headers, + }; + } + return null; +} + +function configureMaxAge(options) { + var maxAge = (typeof options.maxAge === 'number' || options.maxAge) && options.maxAge.toString(); + if (maxAge && maxAge.length) { + return { + key: 'Access-Control-Max-Age', + value: maxAge, + }; + } + return null; +} + +function applyHeaders(headers, res) { + for (var i = 0, n = headers.length; i < n; i++) { + var header = headers[i]; + if (header) { + if (Array.isArray(header)) { + applyHeaders(header, res); + } else if (header.key === 'Vary' && header.value) { + vary(res, header.value); + } else if (header.value) { + res.setHeader(header.key, header.value); + } + } + } +} + +function cors(options, req, res, next) { + var headers = [], + method = req.method && req.method.toUpperCase && req.method.toUpperCase(); + + if (method === 'OPTIONS') { + // preflight + headers.push(configureOrigin(options, req)); + headers.push(configureCredentials(options)); + headers.push(configureMethods(options)); + headers.push(configureAllowedHeaders(options, req)); + headers.push(configureMaxAge(options)); + headers.push(configureExposedHeaders(options)); + applyHeaders(headers, res); + + if (options.preflightContinue) { + next(); + } else { + // Safari (and potentially other browsers) need content-length 0, + // for 204 or they just hang waiting for a body + res.statusCode = options.optionsSuccessStatus; + res.setHeader('Content-Length', '0'); + res.end(); + } + } else { + // actual response + headers.push(configureOrigin(options, req)); + headers.push(configureCredentials(options)); + headers.push(configureExposedHeaders(options)); + applyHeaders(headers, res); + next(); + } +} + +function middlewareWrapper(o) { + // if options are static (either via defaults or custom options passed in), wrap in a function + var optionsCallback = null; + if (typeof o === 'function') { + optionsCallback = o; + } else { + optionsCallback = function (req, cb) { + cb(null, o); + }; + } + + return function corsMiddleware(req, res, next) { + optionsCallback(req, function (err, options) { + if (err) { + next(err); + } else { + var corsOptions = assign({}, defaults, options); + var originCallback = null; + if (corsOptions.origin && typeof corsOptions.origin === 'function') { + originCallback = corsOptions.origin; + } else if (corsOptions.origin) { + originCallback = function (origin, cb) { + cb(null, corsOptions.origin); + }; + } + + if (originCallback) { + originCallback(req.headers.origin, function (err2, origin) { + if (err2 || !origin) { + next(err2); + } else { + corsOptions.origin = origin; + cors(corsOptions, req, res, next); + } + }); + } else { + next(); + } + } + }); + }; +} + +// can pass either an options hash, an options delegate, or nothing +module.exports = middlewareWrapper; + +export default middlewareWrapper; diff --git a/modules/object-assign.ts b/modules/object-assign.ts new file mode 100644 index 0000000..4a93fea --- /dev/null +++ b/modules/object-assign.ts @@ -0,0 +1,97 @@ +// @ts-nocheck + +/* +object-assign +(c) Sindre Sorhus +@license MIT +*/ + +'use strict'; + +/* eslint-disable no-unused-vars */ +var getOwnPropertySymbols = Object.getOwnPropertySymbols; +var hasOwnProperty = Object.prototype.hasOwnProperty; +var propIsEnumerable = Object.prototype.propertyIsEnumerable; + +function toObject(val) { + if (val === null || val === undefined) { + throw new TypeError('Object.assign cannot be called with null or undefined'); + } + + return Object(val); +} + +function shouldUseNative() { + try { + if (!Object.assign) { + return false; + } + + // Detect buggy property enumeration order in older V8 versions. + + // https://bugs.chromium.org/p/v8/issues/detail?id=4118 + var test1 = new String('abc'); // eslint-disable-line no-new-wrappers + // @ts-ignore + test1[5] = 'de'; + if (Object.getOwnPropertyNames(test1)[0] === '5') { + return false; + } + + // https://bugs.chromium.org/p/v8/issues/detail?id=3056 + var test2 = {}; + for (var i = 0; i < 10; i++) { + test2['_' + String.fromCharCode(i)] = i; + } + var order2 = Object.getOwnPropertyNames(test2).map(function (n) { + return test2[n]; + }); + if (order2.join('') !== '0123456789') { + return false; + } + + // https://bugs.chromium.org/p/v8/issues/detail?id=3056 + var test3 = {}; + 'abcdefghijklmnopqrst'.split('').forEach(function (letter) { + test3[letter] = letter; + }); + if (Object.keys(Object.assign({}, test3)).join('') !== 'abcdefghijklmnopqrst') { + return false; + } + + return true; + } catch (err) { + // We don't expect any of the above to throw, but better to be safe. + return false; + } +} + +const assign = shouldUseNative() + ? Object.assign + : function (target, source) { + var from; + var to = toObject(target); + var symbols; + + for (var s = 1; s < arguments.length; s++) { + from = Object(arguments[s]); + + for (var key in from) { + if (hasOwnProperty.call(from, key)) { + to[key] = from[key]; + } + } + + if (getOwnPropertySymbols) { + symbols = getOwnPropertySymbols(from); + for (var i = 0; i < symbols.length; i++) { + if (propIsEnumerable.call(from, symbols[i])) { + to[symbols[i]] = from[symbols[i]]; + } + } + } + } + + return to; + }; + +export default assign; diff --git a/modules/vary.ts b/modules/vary.ts new file mode 100644 index 0000000..3c424ae --- /dev/null +++ b/modules/vary.ts @@ -0,0 +1,147 @@ +// @ts-nocheck + +/*! + * vary + * Copyright(c) 2014-2017 Douglas Christopher Wilson + * MIT Licensed + */ + +'use strict'; + +/** + * Module exports. + */ + +module.exports = vary; +module.exports.append = append; + +/** + * RegExp to match field-name in RFC 7230 sec 3.2 + * + * field-name = token + * token = 1*tchar + * tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" + * / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~" + * / DIGIT / ALPHA + * ; any VCHAR, except delimiters + */ + +var FIELD_NAME_REGEXP = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/; + +/** + * Append a field to a vary header. + * + * @param {String} header + * @param {String|Array} field + * @return {String} + * @public + */ + +function append(header, field) { + if (typeof header !== 'string') { + throw new TypeError('header argument is required'); + } + + if (!field) { + throw new TypeError('field argument is required'); + } + + // get fields array + var fields = !Array.isArray(field) ? parse(String(field)) : field; + + // assert on invalid field names + for (var j = 0; j < fields.length; j++) { + if (!FIELD_NAME_REGEXP.test(fields[j])) { + throw new TypeError('field argument contains an invalid header name'); + } + } + + // existing, unspecified vary + if (header === '*') { + return header; + } + + // enumerate current values + var val = header; + var vals = parse(header.toLowerCase()); + + // unspecified vary + if (fields.indexOf('*') !== -1 || vals.indexOf('*') !== -1) { + return '*'; + } + + for (var i = 0; i < fields.length; i++) { + var fld = fields[i].toLowerCase(); + + // append value (case-preserving) + if (vals.indexOf(fld) === -1) { + vals.push(fld); + val = val ? val + ', ' + fields[i] : fields[i]; + } + } + + return val; +} + +/** + * Parse a vary header into an array. + * + * @param {String} header + * @return {Array} + * @private + */ + +function parse(header) { + var end = 0; + var list = []; + var start = 0; + + // gather tokens + for (var i = 0, len = header.length; i < len; i++) { + switch (header.charCodeAt(i)) { + case 0x20 /* */: + if (start === end) { + start = end = i + 1; + } + break; + case 0x2c /* , */: + list.push(header.substring(start, end)); + start = end = i + 1; + break; + default: + end = i + 1; + break; + } + } + + // final token + list.push(header.substring(start, end)); + + return list; +} + +/** + * Mark that a request is varied on a header field. + * + * @param {Object} res + * @param {String|Array} field + * @public + */ + +function vary(res, field) { + if (!res || !res.getHeader || !res.setHeader) { + // quack quack + throw new TypeError('res argument is required'); + } + + // get existing header + var val = res.getHeader('Vary') || ''; + var header = Array.isArray(val) ? val.join(', ') : String(val); + + // set new header + if ((val = append(header, field))) { + res.setHeader('Vary', val); + } +} + +export default vary; diff --git a/next-env.d.ts b/next-env.d.ts new file mode 100644 index 0000000..fd36f94 --- /dev/null +++ b/next-env.d.ts @@ -0,0 +1,6 @@ +/// +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/next.config.js b/next.config.js new file mode 100644 index 0000000..1575af4 --- /dev/null +++ b/next.config.js @@ -0,0 +1,10 @@ +/** + * @type {import('next').NextConfig} + */ +const nextConfig = { + experimental: { + appDir: true, + }, +}; + +module.exports = nextConfig; diff --git a/package.json b/package.json new file mode 100644 index 0000000..a967353 --- /dev/null +++ b/package.json @@ -0,0 +1,29 @@ +{ + "name": "next-sass-starter", + "engines": { + "node": ">=18" + }, + "license": "MIT", + "version": "1.0.0", + "scripts": { + "dev": "next -p 3005", + "build": "next build", + "start": "next start", + "script": "ts-node -O '{\"module\":\"commonjs\"}' scripts/index.js" + }, + "dependencies": { + "dotenv": "^16.3.1", + "knex": "^2.5.1", + "next": "13.4.12", + "pg": "^8.11.2", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "sass": "1.64.2" + }, + "devDependencies": { + "@types/node": "^20.4.6", + "@types/react": "^18.2.18", + "ts-node": "^10.9.1", + "typescript": "^5.1.6" + } +} diff --git a/pages/api/index.ts b/pages/api/index.ts new file mode 100644 index 0000000..6efe4a9 --- /dev/null +++ b/pages/api/index.ts @@ -0,0 +1,9 @@ +import * as Server from '@common/server'; + +// NOTE(jim): +// CORS API example. +export default async function apiIndex(req, res) { + await Server.cors(req, res); + + res.json({ succes: true }); +} diff --git a/public/favicon-16x16.png b/public/favicon-16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..0058ee0198416522b2357f9920ef534488b0f128 GIT binary patch literal 201 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf6CV09yhEy<4J$SIU$w7qSLV4rv z13h62oW;t28?bqW@Rz0MJe2;{rl_{>dGyp%?$t+Exn-MK%rZ3-lUm%PqAa=fUd~rP zJw3lFYwdG#P8Al>D`a@A!WCNzuKwH@{xe*=PSqU9nf&+VRWUVxMH}C+9MQ?UcY99U yli6dmsb%iPV@|7$t){uKbLh*2~7YPB2Baa literal 0 HcmV?d00001 diff --git a/public/favicon-32x32.png b/public/favicon-32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..75d8a22ef832fd900dc951cf5ca617ba69848276 GIT binary patch literal 388 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>nctVD$EMaSW+oynE2zZ!#kT`-STZ z8b7EftWcS^@L1HM7cK`pR%Z4leUY~FDM)VYwO+Ai+U)}_hb(98aCDwg=jVGm=vK*N z+y47|DuXkBCuy}_3gKGXX>yfUdrI3?lin*ALd`FHIq7iLKHp}+!!y~n{x(;`FK+5uGGS( zVJ&NAc3sYsSf0KNfeb>JwJCS{3p6 zws-Oh>&@%(kM-;;-<2iz!7Kc*!)116>0McFS<&WeLL|P0-S!auK9%cxsLzs+8K>r* gU$!abWb$|UwX4cczwdfA0~optp00i_>zopr0Iiy_3IG5A literal 0 HcmV?d00001 diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..b97dc5b28a665ffc03d32a3ad3f315373f9b66e1 GIT binary patch literal 15086 zcmeI2Piz%M9LJ{?MFlCX74c%5D2TB@H45rM>%XE>DH1e9MM*s10iz)tj7pv{0&0vN zgriXpCf<$lB0+;OA_vc2Y!jo2iJ`_sTSdBlKkx07?XdfH_Vw-VYqHz@8A6P zz1iJ)HKxmSo8e)jcF=sf#+XgUn8CqtywaGTBwMML`RA4xb65)Nq$3lvM?_x7^^K9(f&-&Or~oPF1P$Z{L;ak}KWi^)TR%to6rt zpGd5Bqel)u5O-#9oF#v9elm=)JVT5us*gzdJ8_4n(d*J<4@!g09p9sA$_t!l4W~q( zHLR6h+EXLBKP8g;17Z=_rXb&oK5J-}54rCasYg+{+kD2=IW4jl7b_&VNgN4DADN3? z5>SVtbGLbnsT0kAwZ<=sC&k~yzeCbT#vD2p*xqXgCAW7f{x{+|F-RbD=%Cw4xI@YO zC?@6Y%F)H96Tma(*P@$a+87&b%f>z>(Cjmlv<_^rEfdQW{J5BuW11XWe3S+5Prrz1 zc_z(ci;s4LXQ-FNBjRC)Z)nVZ4sI9!6qE8xlVghyut9uBeAnT78rQ@tMPKwv@dfdq z_^QKKH0F2-Y!(-qKR&=3ald#VhI=LB*|*)eP7Z$-)9Rcwk1amR!et8lg_x9InjBkv zl!<2*{H&Okt2>Vkwq>JF3AAzzu)($yuu)0;Ai6b98l#I%+2Hvd0`|Yp#ec=5yqQA> zT@bZzNQ(WtUEy{`LbAs_Eq*3Wh-X65N5))qAHDW&HC&XzZ`Sw>=N3k z*Dge4_G}PL(4Gr|6SOj`(FQXE4L_4h8|_!!jrO&!`7OHVIjgsc$=dmGbZ)+# z^<(|oIAL6yht138IUUsMc~1qk`Z`Fr9zU@S!ud=s2Wf-ih&cCuTG2N=$tx`S3S6NT zz_ReD0{gym<&onPa{724jzgbm?;Aex(|j~bf2$^MX`+x8|2Em;qg8B>;Zc#ZmN<+` znDjgACe2}Mf0Kx__mOcP$h&J?9l9llE;dWhj@Z^O>wi=14+XSi;(?I#?Hs=y9ubGc zb^`yLbv-D4EB+(u&QT|QWXz${E9QYMa{8-io9&6V0t zd&EZv-B!Rk%65_SGRWBSn#&XB#pxbWn^mJTw-jcLcs zjfi=GeeF{5F7Zn-dM)T9V-B5b#CC$c|84QA&?#pR_lSE#(zoXkeD~iH?eEv>E(q-X z$Hd_nFH0o5I7ZGr@2JQ6Xt!y_)m(BJ^KXDAW0wx zeB_0+_<6;(e#?%(Rf&w-WJ+i`lZO=+X$5qs&>S@#WSr0;#)J+qW(I=bkDegls3VTd zp@S|q*rs5e>c-7-P@FrbU{@^`mna-;6)fY14947}`Xl=5)gOwCnQQh8+oyScFI*Mc z(AsTs?%oJ(r1)&zO8e|!>>J~Hb|-KIeQjA)-f=r`Mss4@O|uO!CDqP4XxGS;(E zyitr^7w)yxnOae!k3?%{TV%{}?^!Fx-Jd0{V@!X47p*OQWImvNmsUZIo7K5hzjJmMp2He5TQlaryN#I{ LXpla#INkpN_%t|E literal 0 HcmV?d00001 diff --git a/scripts/example.js b/scripts/example.js new file mode 100644 index 0000000..c88f3f9 --- /dev/null +++ b/scripts/example.js @@ -0,0 +1,33 @@ +import DB from '../data/db'; + +const NAME = `example.js`; + +console.log(`RUNNING: ${NAME}`); + +const createRun = DB.schema.createTable('market', function(table) { + table + .uuid('id') + .primary() + .unique() + .notNullable() + .defaultTo(DB.raw('uuid_generate_v4()')); + table.string('text').nullable(); + table.jsonb('data').nullable(); + table + .timestamp('created_at') + .notNullable() + .defaultTo(DB.raw('now()')); + table + .timestamp('updated_at') + .notNullable() + .defaultTo(DB.raw('now()')); + table.timestamp('deleted_at').nullable(); +}); + +async function run() { + await Promise.all([createRun]); + console.log(`FINISHED: ${NAME}`); + process.exit(0); +} + +run(); diff --git a/scripts/index.js b/scripts/index.js new file mode 100644 index 0000000..ea6796e --- /dev/null +++ b/scripts/index.js @@ -0,0 +1 @@ +module.exports = require('./' + process.argv[2] + '.js'); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..b6c2084 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,59 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@root/*": [ + "./*" + ], + "@common/*": [ + "./common/*" + ], + "@data/*": [ + "./data/*" + ], + "@components/*": [ + "./components/*" + ], + "@pages/*": [ + "./pages/*" + ], + "@modules/*": [ + "./modules/*" + ] + }, + "target": "es5", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "strict": false, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "strictNullChecks": true + }, + "exclude": [ + "node_modules", + "**/*.spec.ts" + ], + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts" + ] +}