@typetron/framework 0.5.3 → 0.5.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc-old.js +208 -0
- package/Cache/Cache.ts +13 -0
- package/Cache/Stores/DatabaseStore.ts +120 -0
- package/Cache/Stores/FileStore.ts +69 -0
- package/Cache/Stores/MemoryStore.ts +71 -0
- package/Cache/index.ts +5 -0
- package/Cache/types.ts +4 -0
- package/Container/ClassResolver.ts +106 -0
- package/Container/Container.ts +144 -0
- package/Container/Contracts.ts +3 -0
- package/Container/Decorators.ts +20 -0
- package/Container/Metadata.ts +16 -0
- package/Container/Resolver.ts +27 -0
- package/Container/index.ts +5 -0
- package/Database/Clauses/Where.ts +0 -0
- package/Database/Connection.ts +52 -0
- package/Database/Decorators.ts +142 -0
- package/Database/Drivers/DatabaseDriver.ts +37 -0
- package/Database/Drivers/MySQL/Alter/index.ts +55 -0
- package/Database/Drivers/MySQL/ColumnDefinition.ts +16 -0
- package/Database/Drivers/MySQL/Create.ts +47 -0
- package/Database/Drivers/MySQL/MysqlDriver.ts +123 -0
- package/Database/Drivers/MySQL/Schema.ts +82 -0
- package/Database/Drivers/MySQL/index.ts +13 -0
- package/Database/Drivers/SQL/Schema.ts +73 -0
- package/Database/Drivers/SQL/Statements/Alter.ts +22 -0
- package/Database/Drivers/SQL/Statements/BaseStatement.ts +55 -0
- package/Database/Drivers/SQL/Statements/Delete.ts +20 -0
- package/Database/Drivers/SQL/Statements/Insert.ts +24 -0
- package/Database/Drivers/SQL/Statements/Select.ts +55 -0
- package/Database/Drivers/SQL/Statements/Update.ts +33 -0
- package/Database/Drivers/SQL/index.ts +8 -0
- package/Database/Drivers/SQLite/Create.ts +45 -0
- package/Database/Drivers/SQLite/Schema.ts +54 -0
- package/Database/Drivers/SQLite/SqliteDriver.ts +144 -0
- package/Database/Drivers/SQLite/index.ts +15 -0
- package/Database/Drivers/SchemaContract.ts +11 -0
- package/Database/Drivers/Statement.ts +16 -0
- package/Database/Drivers/index.ts +4 -0
- package/Database/Entity.ts +377 -0
- package/Database/EntityNotFoundError.ts +5 -0
- package/Database/EntityProxyHandler.ts +43 -0
- package/Database/EntityQuery.ts +197 -0
- package/Database/Expression.ts +3 -0
- package/Database/Fields.ts +816 -0
- package/Database/Helpers.ts +12 -0
- package/Database/List.ts +71 -0
- package/Database/Migrations/Migration.ts +10 -0
- package/Database/Migrations/MigrationHistory.ts +20 -0
- package/Database/Migrations/Migrator.ts +122 -0
- package/Database/Migrations/index.ts +3 -0
- package/Database/ORM/BaseRelationship.ts +10 -0
- package/Database/Query.ts +374 -0
- package/Database/Seeders/Seeder.ts +3 -0
- package/Database/Seeders/SeederManager.ts +27 -0
- package/Database/Seeders/index.ts +2 -0
- package/Database/StringExpression.ts +9 -0
- package/Database/Types.ts +165 -0
- package/Database/index.ts +23 -0
- package/Encryption/index.ts +51 -0
- package/Forms/Decorators.ts +29 -0
- package/Forms/Form.ts +70 -0
- package/Forms/FormFields.ts +36 -0
- package/Forms/index.ts +3 -0
- package/Framework/App.ts +10 -0
- package/Framework/Application.ts +118 -0
- package/Framework/Auth/Auth.ts +122 -0
- package/Framework/Auth/Authenticatable.ts +9 -0
- package/Framework/Auth/User.ts +27 -0
- package/Framework/Auth/index.ts +29 -0
- package/Framework/Config/AppConfig.ts +25 -0
- package/Framework/Config/AuthConfig.ts +9 -0
- package/Framework/Config/BaseConfig.ts +9 -0
- package/Framework/Config/CacheConfig.ts +38 -0
- package/Framework/Config/DatabaseConfig.ts +21 -0
- package/Framework/Config/MailConfig.ts +30 -0
- package/Framework/Config/index.ts +6 -0
- package/Framework/Middleware/AuthMiddleware.ts +31 -0
- package/Framework/Middleware/CorsMiddleware.ts +32 -0
- package/Framework/Middleware/StaticAssetsMiddleware.ts +76 -0
- package/Framework/Middleware/index.ts +2 -0
- package/Framework/Provider.ts +9 -0
- package/Framework/Providers/CacheProvider.ts +28 -0
- package/Framework/Providers/DatabaseProvider.ts +36 -0
- package/Framework/Providers/MailProvider.ts +31 -0
- package/Framework/Providers/WebsocketsProvider.ts +120 -0
- package/Framework/Resolvers/AuthResolver.ts +23 -0
- package/Framework/Resolvers/EntityResolver.ts +41 -0
- package/Framework/Resolvers/FormResolver.ts +37 -0
- package/Framework/RootDir.ts +1 -0
- package/Framework/index.ts +8 -0
- package/Frontend/Angular/index.ts +24 -0
- package/Frontend/Websockets/index.ts +86 -0
- package/Mail/MailAddress.ts +4 -0
- package/Mail/MailTransport.ts +6 -0
- package/Mail/Mailable.ts +16 -0
- package/Mail/Mailer.ts +27 -0
- package/Mail/PendingMail.ts +68 -0
- package/Mail/SentMessage.ts +20 -0
- package/Mail/Transports/MemoryTransport.ts +13 -0
- package/Mail/Transports/SendGridTransport.ts +95 -0
- package/Mail/index.ts +3 -0
- package/Models/Model.ts +41 -0
- package/Models/index.ts +54 -0
- package/Router/BaseController.ts +7 -0
- package/Router/Decorators.ts +224 -0
- package/Router/Guard.ts +10 -0
- package/Router/Http/Contracts.ts +4 -0
- package/Router/Http/ErrorHandler.ts +45 -0
- package/Router/Http/ErrorHandlerInterface.ts +6 -0
- package/Router/Http/Errors/HttpError.ts +15 -0
- package/Router/Http/Handler.ts +183 -0
- package/Router/Http/HttpRoute.ts +110 -0
- package/Router/Http/Middleware.ts +7 -0
- package/Router/Http/Request.ts +225 -0
- package/Router/Http/Response.ts +35 -0
- package/Router/Http/index.ts +85 -0
- package/Router/Metadata.ts +34 -0
- package/Router/Middleware.ts +16 -0
- package/Router/Request.ts +65 -0
- package/Router/Route.ts +16 -0
- package/Router/RouteNotFoundError.ts +5 -0
- package/Router/Router.ts +35 -0
- package/Router/Servers/index.ts +6 -0
- package/Router/Servers/node.ts +49 -0
- package/Router/Servers/uNetworking.ts +151 -0
- package/Router/Websockets/Handler.ts +82 -0
- package/Router/Websockets/Middleware.ts +7 -0
- package/Router/Websockets/Request.ts +3 -0
- package/Router/Websockets/WebsocketRoute.ts +61 -0
- package/Router/Websockets/index.ts +57 -0
- package/Router/Websockets/types.ts +23 -0
- package/Router/index.ts +6 -0
- package/Storage/File.ts +30 -0
- package/Storage/Image.ts +19 -0
- package/Storage/InvalidImage.ts +3 -0
- package/Storage/Storage.ts +98 -0
- package/Storage/index.ts +4 -0
- package/Storage/utils.ts +18 -0
- package/Support/Array.ts +184 -0
- package/Support/Math.ts +12 -0
- package/Support/Metadata.ts +31 -0
- package/Support/String.ts +38 -0
- package/Support/index.ts +19 -0
- package/Support/utils.ts +7 -0
- package/Testing/TestCase.ts +193 -0
- package/Validation/Rule.ts +9 -0
- package/Validation/RuleInterface.ts +10 -0
- package/Validation/Rules/Email.ts +20 -0
- package/Validation/Rules/FileExtension.ts +21 -0
- package/Validation/Rules/MaxLength.ts +17 -0
- package/Validation/Rules/MinLength.ts +18 -0
- package/Validation/Rules/Optional.ts +18 -0
- package/Validation/Rules/Required.ts +29 -0
- package/Validation/Validator.ts +7 -0
- package/Validation/index.ts +12 -0
- package/benchmark-index.js +20 -0
- package/benchmark.ts +47 -0
- package/package.json +7 -3
- package/tests/Cache/DatabaseStoreTest.ts +110 -0
- package/tests/Cache/FileStoreTest.ts +93 -0
- package/tests/Cache/MemoryStoreTest.ts +92 -0
- package/tests/Container/ContainerTest.ts +336 -0
- package/tests/Database/Drivers/MySQL/AlterTest.ts +100 -0
- package/tests/Database/Drivers/MySQL/CreateTest.ts +39 -0
- package/tests/Database/Drivers/MysqlDriverTest.ts +140 -0
- package/tests/Database/Drivers/SqliteDriverTest.ts +126 -0
- package/tests/Database/Entities/Article.ts +21 -0
- package/tests/Database/Entities/Profile.ts +21 -0
- package/tests/Database/Entities/Role.ts +15 -0
- package/tests/Database/Entities/User.ts +35 -0
- package/tests/Database/EntityRelationshipsTest.ts +475 -0
- package/tests/Database/EntityTest.ts +202 -0
- package/tests/Database/Migrations/RunnerTest.ts +96 -0
- package/tests/Database/Migrations/migrations/1.createUserTable.ts +12 -0
- package/tests/Database/Migrations/migrations/2.createArticlesTable.ts +12 -0
- package/tests/Database/QueryTest.ts +573 -0
- package/tests/Database/SchemaTest.ts +32 -0
- package/tests/Database/Seeders/RunnerTest.ts +30 -0
- package/tests/Database/Seeders/seeders/RandomSeeder.ts +10 -0
- package/tests/Database/utils.ts +16 -0
- package/tests/Forms/FormsTest.ts +59 -0
- package/tests/Framework/Middleware/StaticAssetsMiddlewareTest.ts +129 -0
- package/tests/Framework/Resolvers/EntityResolverTest.ts +48 -0
- package/tests/Framework/Router/RouterTest.ts +33 -0
- package/tests/Mail/MailerTest.ts +76 -0
- package/tests/Model/ModelTest.ts +183 -0
- package/tests/Router/HttpHandlerTest.ts +72 -0
- package/tests/Router/RequestTest.ts +77 -0
- package/tests/Router/WebSocketsHandlerTest.ts +51 -0
- package/tests/Storage/StorageTest.ts +97 -0
- package/tests/Support/ArrayTest.ts +167 -0
- package/tests/Support/StringTest.ts +18 -0
- package/tests/Validation/RulesTest.ts +54 -0
- package/types.d.ts +8 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { Query } from './Query'
|
|
2
|
+
import { StringExpression } from './StringExpression'
|
|
3
|
+
import { EntityQuery } from './EntityQuery'
|
|
4
|
+
|
|
5
|
+
export enum Operators {
|
|
6
|
+
'=' = '=',
|
|
7
|
+
'<' = '<',
|
|
8
|
+
'<=' = '<=',
|
|
9
|
+
'>' = '>',
|
|
10
|
+
'>=' = '>=',
|
|
11
|
+
'!=' = '!=',
|
|
12
|
+
'AND' = 'AND',
|
|
13
|
+
'OR' = 'OR',
|
|
14
|
+
'IS' = 'IS',
|
|
15
|
+
'IN' = 'IN',
|
|
16
|
+
'NOT IN' = 'NOT IN',
|
|
17
|
+
'LIKE' = 'LIKE',
|
|
18
|
+
'IS NOT' = 'IS NOT',
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export enum JoinType {
|
|
22
|
+
INNER = 'INNER',
|
|
23
|
+
LEFT = 'LEFT',
|
|
24
|
+
RIGHT = 'RIGHT'
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type BooleanOperator = 'AND' | 'OR' | 'XOR';
|
|
28
|
+
export type Operator = keyof typeof Operators;
|
|
29
|
+
export type SqlValue = string | number | Date | undefined;
|
|
30
|
+
export type SqlValueMap = {[key: string]: SqlValue};
|
|
31
|
+
|
|
32
|
+
class JoinClause {
|
|
33
|
+
type: keyof typeof JoinType
|
|
34
|
+
table: string
|
|
35
|
+
first: string
|
|
36
|
+
operator: Operator
|
|
37
|
+
second: string
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface BaseComponents {
|
|
41
|
+
table: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface CreateComponents {
|
|
45
|
+
table: string;
|
|
46
|
+
columns: (string | StringExpression)[];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface SelectComponents extends BaseComponents {
|
|
50
|
+
distinct?: boolean;
|
|
51
|
+
columns: (string | StringExpression)[];
|
|
52
|
+
aggregate?: {
|
|
53
|
+
function: string,
|
|
54
|
+
columns: string[];
|
|
55
|
+
};
|
|
56
|
+
joins: JoinClause[];
|
|
57
|
+
wheres: SqlClause[];
|
|
58
|
+
groups: string[];
|
|
59
|
+
orders: [string, Direction][];
|
|
60
|
+
having: Where[];
|
|
61
|
+
limit?: {from: number, count?: number};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface InsertComponents extends BaseComponents {
|
|
65
|
+
insert?: SqlValueMap[];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface UpdateComponents extends BaseComponents {
|
|
69
|
+
update?: SqlValueMap;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface Components extends BaseComponents, SelectComponents, InsertComponents, UpdateComponents, CreateComponents {
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// export interface SelectComponents extends Components {
|
|
76
|
+
// distinct = false;
|
|
77
|
+
// select: string[] = ['*'];
|
|
78
|
+
// insert?: {};
|
|
79
|
+
// delete?: boolean;
|
|
80
|
+
// joins?: JoinClause[];
|
|
81
|
+
// wheres?: Where[];
|
|
82
|
+
// orderBy?: OrderBy[];
|
|
83
|
+
// }
|
|
84
|
+
|
|
85
|
+
export type WhereFunction = (query: Query) => void;
|
|
86
|
+
export type WhereCondition = {[key: string]: string | number};
|
|
87
|
+
// export type WhereValue = SqlValue | WhereFunction | WhereCondition;
|
|
88
|
+
export type WhereValue = SqlValue;
|
|
89
|
+
|
|
90
|
+
export type Direction = 'ASC' | 'DESC';
|
|
91
|
+
|
|
92
|
+
export interface SqlClause {
|
|
93
|
+
toSql(): string;
|
|
94
|
+
|
|
95
|
+
getValues(): SqlValue[];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
class SqlExpression implements SqlClause {
|
|
99
|
+
constructor(private sql: string, private values: SqlValue[] = []) {}
|
|
100
|
+
|
|
101
|
+
toSql() {
|
|
102
|
+
return this.sql
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
getValues() {
|
|
106
|
+
return this.values
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export class WhereExpression extends SqlExpression {
|
|
111
|
+
constructor(public column: string, public expression: SqlExpression, public boolean: BooleanOperator) {
|
|
112
|
+
super(`${boolean} ${column} ${expression.toSql()}`, expression.getValues())
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export class Where extends WhereExpression {
|
|
117
|
+
constructor(column: string, operator: Operator, value: SqlValue, boolean: BooleanOperator) {
|
|
118
|
+
super(column, new SqlExpression(`${operator} ?`, [value]), boolean)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export class WhereNested {
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export class WhereBetween extends WhereExpression {
|
|
126
|
+
constructor(column: string, values: [SqlValue, SqlValue], boolean: BooleanOperator, not = false) {
|
|
127
|
+
super(column, new SqlExpression(`${not ? 'NOT ' : ''}BETWEEN ? AND ?`, values), boolean)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export class WhereLike extends WhereExpression {
|
|
132
|
+
constructor(column: string, value: SqlValue, boolean: BooleanOperator, not = false) {
|
|
133
|
+
super(column, new SqlExpression(`${not ? 'NOT ' : ''}LIKE ?`, [value]), boolean)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export class WhereIn extends WhereExpression {
|
|
138
|
+
constructor(column: string, values: WhereValue[], boolean: BooleanOperator, not = false) {
|
|
139
|
+
const value = values.map(() => '?').join(', ')
|
|
140
|
+
if (values.length) {
|
|
141
|
+
super(column, new SqlExpression(`${not ? 'NOT ' : ''}IN (${value})`, values), boolean)
|
|
142
|
+
} else {
|
|
143
|
+
super('', new SqlExpression('1 = 0', values), boolean)
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export class WhereNull extends WhereExpression {
|
|
149
|
+
constructor(column: string, boolean: BooleanOperator, not = false) {
|
|
150
|
+
super(column, new SqlExpression(`IS ${not ? 'NOT ' : ''}NULL`), boolean)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export class WhereSubSelect extends WhereExpression {
|
|
155
|
+
constructor(column: string, operator: Operator, public query: Query, boolean: BooleanOperator) {
|
|
156
|
+
super(column, new SqlExpression(`${operator} (${query.toSQL()})`, query.getBindings()), boolean)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// tslint:disable-next-line:no-any
|
|
161
|
+
export type RelationsTreeQuery = (query: EntityQuery<any>) => void
|
|
162
|
+
|
|
163
|
+
export interface RelationsTree {
|
|
164
|
+
[key: string]: RelationsTree | RelationsTreeQuery
|
|
165
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ChildKeys, ChildObject, Constructor } from '@Typetron/Support'
|
|
2
|
+
import { Entity } from './Entity'
|
|
3
|
+
import { BaseRelationship } from './ORM/BaseRelationship'
|
|
4
|
+
|
|
5
|
+
export type EntityKeys<T extends Entity> = ChildKeys<T, Entity>
|
|
6
|
+
export type EntityColumns<T extends Entity> = { [P in EntityKeys<T>]: T[P] extends Function ? never : P }[ EntityKeys<T>]
|
|
7
|
+
|
|
8
|
+
export type EntityObject<T extends Entity> = ChildObject<{
|
|
9
|
+
[P in keyof T]: T[P] extends BaseRelationship<infer U> ? U | number : T[P]
|
|
10
|
+
}, Entity>
|
|
11
|
+
export type EntityConstructor<T extends Entity> = typeof Entity & Constructor<T>
|
|
12
|
+
// export type EntityConstructor<T extends Entity> = {[key in keyof typeof Entity]: (typeof Entity)[key]} & Constructor<T>;
|
|
13
|
+
export type DotNotationProperties<T> = string
|
|
14
|
+
|
|
15
|
+
export * from './Query'
|
|
16
|
+
export * from './Entity'
|
|
17
|
+
export * from './Decorators'
|
|
18
|
+
export * from './StringExpression'
|
|
19
|
+
export * from './Expression'
|
|
20
|
+
export * from './Drivers'
|
|
21
|
+
export * from './Fields'
|
|
22
|
+
export * from './Connection'
|
|
23
|
+
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { compare, hash } from 'bcryptjs'
|
|
2
|
+
import * as jwt from 'jsonwebtoken'
|
|
3
|
+
import { GetPublicKeyOrSecret, Secret, SignOptions, VerifyErrors, VerifyOptions } from 'jsonwebtoken'
|
|
4
|
+
|
|
5
|
+
export class Crypt {
|
|
6
|
+
hash(value: string, saltRounds: number): Promise<string> {
|
|
7
|
+
return hash(value, saltRounds)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
compare(value: string, encrypted: string): Promise<boolean> {
|
|
11
|
+
return compare(value, encrypted)
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface JWToken<T> {
|
|
16
|
+
sub: T
|
|
17
|
+
iat: number
|
|
18
|
+
exp: number
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class JWT {
|
|
22
|
+
sign(payload: string | Buffer | object, secretOrPrivateKey: Secret, options?: SignOptions) {
|
|
23
|
+
return new Promise<string>((resolve, reject) => {
|
|
24
|
+
jwt.sign(
|
|
25
|
+
payload,
|
|
26
|
+
secretOrPrivateKey,
|
|
27
|
+
options ?? {},
|
|
28
|
+
(error: Error | null, encoded?: string) => {
|
|
29
|
+
error ? reject(error) : resolve(encoded as string)
|
|
30
|
+
}
|
|
31
|
+
)
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
verify<T>(token: string, secretOrPrivateKey: Secret | GetPublicKeyOrSecret, options?: VerifyOptions) {
|
|
36
|
+
return new Promise<JWToken<T>>((resolve, reject) => {
|
|
37
|
+
jwt.verify(
|
|
38
|
+
token,
|
|
39
|
+
secretOrPrivateKey,
|
|
40
|
+
{
|
|
41
|
+
...options,
|
|
42
|
+
complete: true
|
|
43
|
+
},
|
|
44
|
+
(error: VerifyErrors | null, decoded) => {
|
|
45
|
+
error ? reject(error) : resolve(decoded?.payload as any as JWToken<T>)
|
|
46
|
+
}
|
|
47
|
+
)
|
|
48
|
+
})
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { RuleInterface } from '../Validation'
|
|
2
|
+
import { Type } from '../Support'
|
|
3
|
+
import { FormField } from './FormFields'
|
|
4
|
+
import { Form } from './Form'
|
|
5
|
+
|
|
6
|
+
export const FormMetadataKey = 'form:fields'
|
|
7
|
+
|
|
8
|
+
export function Field<T extends Form>(name?: string) {
|
|
9
|
+
return function(target: T, property: string) {
|
|
10
|
+
const fields: {[key: string]: FormField} = Reflect.getMetadata(FormMetadataKey, target.constructor) || {}
|
|
11
|
+
const type = Reflect.getMetadata('design:type', target, property)
|
|
12
|
+
const field = fields[property] || new FormField(name || property, type)
|
|
13
|
+
field.name = name || property
|
|
14
|
+
fields[property] = field
|
|
15
|
+
Reflect.defineMetadata(FormMetadataKey, fields, target.constructor)
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// tslint:disable-next-line:no-any
|
|
20
|
+
export function Rules(...rules: (Type<RuleInterface> | ((...args: any[]) => Type<RuleInterface>))[]) {
|
|
21
|
+
return function(target: Object, property: string) {
|
|
22
|
+
const fields: {[key: string]: FormField} = Reflect.getMetadata(FormMetadataKey, target.constructor) || {}
|
|
23
|
+
const type = Reflect.getMetadata('design:type', target, property)
|
|
24
|
+
const field = fields[property] || new FormField(property, type)
|
|
25
|
+
field.rules = rules
|
|
26
|
+
fields[property] = field
|
|
27
|
+
Reflect.defineMetadata(FormMetadataKey, fields, target.constructor)
|
|
28
|
+
}
|
|
29
|
+
}
|
package/Forms/Form.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { FormMetadataKey } from './Decorators'
|
|
2
|
+
import { RuleValue } from '../Validation'
|
|
3
|
+
import { ChildKeys, ChildObject, Constructor } from '../Support'
|
|
4
|
+
import { FormField } from './FormFields'
|
|
5
|
+
import { Injectable, Scope } from '../Container'
|
|
6
|
+
|
|
7
|
+
export type FormFields<T> = ChildObject<T, Form>;
|
|
8
|
+
|
|
9
|
+
@Injectable(Scope.TRANSIENT)
|
|
10
|
+
export abstract class Form {
|
|
11
|
+
|
|
12
|
+
readonly errors: {[key: string]: Record<string, string>} = {}
|
|
13
|
+
|
|
14
|
+
static fields<T extends Form>(this: Constructor<T>): Record<ChildKeys<T, Form>, FormField> {
|
|
15
|
+
return Reflect.getMetadata(FormMetadataKey, this)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
fields() {
|
|
19
|
+
return (this.constructor as (Constructor<Form> & typeof Form)).fields()
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
valid() {
|
|
23
|
+
const fields = Object.values(this.fields()) as FormField[]
|
|
24
|
+
fields.forEach(field => {
|
|
25
|
+
const errors = field.validate(this[field.name as keyof FormFields<this>])
|
|
26
|
+
if (errors) {
|
|
27
|
+
this.errors[field.name] = errors
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
return !Object.keys(this.errors).length
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
validated() {
|
|
35
|
+
const fields = Object.values(this.fields()) as FormField[]
|
|
36
|
+
return fields.filter(field => !this.errors[field.name])
|
|
37
|
+
.reduce((obj, field) => {
|
|
38
|
+
const value = this[field.name as keyof this]
|
|
39
|
+
if (value) {
|
|
40
|
+
obj[field.name] = value
|
|
41
|
+
}
|
|
42
|
+
return obj
|
|
43
|
+
}, <{[key: string]: RuleValue}>{})
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
value() {
|
|
47
|
+
const fields = Object.values(this.fields()) as FormField[]
|
|
48
|
+
// tslint:disable-next-line:no-any
|
|
49
|
+
const value: Record<string, any> = {}
|
|
50
|
+
fields.forEach(field => {
|
|
51
|
+
value[field.name] = this[field.name as keyof FormFields<this>]
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
return value
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
fill(data: Partial<FormFields<this>>) {
|
|
58
|
+
const fields = Object.values(this.fields()) as FormField[]
|
|
59
|
+
fields.forEach(field => {
|
|
60
|
+
if (field.name in data) {
|
|
61
|
+
// @ts-ignore
|
|
62
|
+
this[field.name] = data[field.name]
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
toJSON() {
|
|
68
|
+
return this.value()
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Constructor, Type } from '../Support'
|
|
2
|
+
import { Optional, RuleInterface } from '../Validation'
|
|
3
|
+
|
|
4
|
+
export class FormField {
|
|
5
|
+
constructor(
|
|
6
|
+
public name: string,
|
|
7
|
+
// tslint:disable-next-line:no-any
|
|
8
|
+
public type: any,
|
|
9
|
+
// tslint:disable-next-line:no-any
|
|
10
|
+
public rules: (Type<RuleInterface> | ((...args: any[]) => Type<RuleInterface>)) [] = []
|
|
11
|
+
) {
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// tslint:disable-next-line:no-any
|
|
15
|
+
validate(value: any): Record<string, string> | undefined {
|
|
16
|
+
const errors: Record<string, string> = {}
|
|
17
|
+
let hasErrors = false
|
|
18
|
+
|
|
19
|
+
if (this.rules.includes(Optional) && (value === undefined || value === null)) {
|
|
20
|
+
return
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
this.rules.forEach(rule => {
|
|
24
|
+
if (!rule.prototype.passes) {
|
|
25
|
+
rule = (rule as Function)()
|
|
26
|
+
}
|
|
27
|
+
const instance = new (rule as Constructor<RuleInterface>)
|
|
28
|
+
if (!instance.passes(this.name, value)) {
|
|
29
|
+
hasErrors = true
|
|
30
|
+
errors[instance.identifier] = instance.message(this.name, value)
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
return hasErrors ? errors : undefined
|
|
35
|
+
}
|
|
36
|
+
}
|
package/Forms/index.ts
ADDED
package/Framework/App.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Application } from './Application'
|
|
2
|
+
import { ServiceIdentifier } from '../Container/Contracts'
|
|
3
|
+
|
|
4
|
+
export class App {
|
|
5
|
+
static instance: Application
|
|
6
|
+
|
|
7
|
+
static get<T>(service: ServiceIdentifier<T> | string): T {
|
|
8
|
+
return App.instance.get(service)
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { Container } from '../Container'
|
|
2
|
+
import { Type } from '../Support'
|
|
3
|
+
import { App } from './App'
|
|
4
|
+
import { Provider } from './Provider'
|
|
5
|
+
import { AppConfig, AuthConfig, BaseConfig } from './Config'
|
|
6
|
+
import { FormResolver } from './Resolvers/FormResolver'
|
|
7
|
+
import { EntityResolver } from './Resolvers/EntityResolver'
|
|
8
|
+
import { RootDir } from './RootDir'
|
|
9
|
+
import { StaticAssetsMiddleware } from './Middleware/StaticAssetsMiddleware'
|
|
10
|
+
import { ErrorHandler, ErrorHandlerInterface, Handler as HttpHandler, } from '../Router/Http'
|
|
11
|
+
import { Storage } from '../Storage'
|
|
12
|
+
import { AuthResolver } from './Resolvers/AuthResolver'
|
|
13
|
+
import fileSystem from 'fs'
|
|
14
|
+
import path from 'path'
|
|
15
|
+
import { WebsocketsProvider } from './Providers/WebsocketsProvider'
|
|
16
|
+
|
|
17
|
+
export class Application extends Container {
|
|
18
|
+
static defaultConfigDirectory = 'config'
|
|
19
|
+
|
|
20
|
+
constructor(
|
|
21
|
+
public directory: string,
|
|
22
|
+
public configDirectory = Application.defaultConfigDirectory
|
|
23
|
+
) {
|
|
24
|
+
super()
|
|
25
|
+
Application.setInstance(this)
|
|
26
|
+
this.set(Application, (App.instance = this))
|
|
27
|
+
this.set(Container, this)
|
|
28
|
+
this.set(RootDir, directory)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
static async create(
|
|
32
|
+
directory: string,
|
|
33
|
+
configDirectory = Application.defaultConfigDirectory
|
|
34
|
+
) {
|
|
35
|
+
const app = new this(
|
|
36
|
+
fileSystem.realpathSync.native(directory),
|
|
37
|
+
configDirectory
|
|
38
|
+
)
|
|
39
|
+
await app.bootstrap()
|
|
40
|
+
return app
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async startServer() {
|
|
44
|
+
const httpHandler = this.get(HttpHandler)
|
|
45
|
+
|
|
46
|
+
const appConfig = this.get(AppConfig)
|
|
47
|
+
|
|
48
|
+
if (appConfig.websocketsPort) {
|
|
49
|
+
await this.registerProviders([WebsocketsProvider])
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return httpHandler.startServer(this)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public async registerProviders(providers: Type<Provider>[]) {
|
|
56
|
+
await Promise.all(
|
|
57
|
+
providers.map((provider) => {
|
|
58
|
+
const instance = this.get(provider)
|
|
59
|
+
return instance.register()
|
|
60
|
+
})
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
private async loadConfig(configDirectory: string) {
|
|
65
|
+
const configsPath = path.join(this.directory, configDirectory)
|
|
66
|
+
|
|
67
|
+
const storage = this.get(Storage)
|
|
68
|
+
|
|
69
|
+
if (!(await storage.exists(configsPath))) {
|
|
70
|
+
console.warn(
|
|
71
|
+
`Config path '${configsPath}' does not exist. Running with default config.`
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
storage
|
|
76
|
+
.files(configsPath)
|
|
77
|
+
.whereIn('extension', ['ts'])
|
|
78
|
+
.forEach((file) => {
|
|
79
|
+
const configItem = require(file.path).default as BaseConfig<{}>
|
|
80
|
+
if (configItem && configItem.constructor) {
|
|
81
|
+
configItem.applyNewValues()
|
|
82
|
+
this.set(configItem.constructor, configItem)
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private registerResolvers() {
|
|
88
|
+
this.resolvers.unshift(new FormResolver(this))
|
|
89
|
+
this.resolvers.unshift(new EntityResolver(this))
|
|
90
|
+
this.resolvers.unshift(new AuthResolver(this))
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private async bootstrap() {
|
|
94
|
+
await this.loadConfig(this.configDirectory)
|
|
95
|
+
|
|
96
|
+
this.set(ErrorHandlerInterface, this.get(ErrorHandler))
|
|
97
|
+
|
|
98
|
+
const appConfig = this.get(AppConfig)
|
|
99
|
+
await this.checkAppSecret()
|
|
100
|
+
|
|
101
|
+
if (appConfig.staticAssets) {
|
|
102
|
+
appConfig.middleware.http.unshift(StaticAssetsMiddleware)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
this.registerResolvers()
|
|
106
|
+
|
|
107
|
+
const providers = appConfig.providers || []
|
|
108
|
+
|
|
109
|
+
await this.registerProviders(providers)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private async checkAppSecret() {
|
|
113
|
+
const authConfig = this.get(AuthConfig)
|
|
114
|
+
if (!authConfig.signature) {
|
|
115
|
+
throw new Error(`APP_SECRET is not setup in your '.env' file.`)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { Inject, Injectable, Scope } from '../../Container'
|
|
2
|
+
import { User } from './User'
|
|
3
|
+
import { AuthConfig } from '../Config'
|
|
4
|
+
import { EntityKeys, ID } from '../../Database'
|
|
5
|
+
import { Crypt, JWT, JWToken } from '@Typetron/Encryption'
|
|
6
|
+
|
|
7
|
+
@Injectable(Scope.REQUEST)
|
|
8
|
+
export class Auth {
|
|
9
|
+
|
|
10
|
+
@Inject()
|
|
11
|
+
authConfig: AuthConfig
|
|
12
|
+
|
|
13
|
+
@Inject()
|
|
14
|
+
jwt: JWT
|
|
15
|
+
|
|
16
|
+
@Inject()
|
|
17
|
+
crypt: Crypt
|
|
18
|
+
|
|
19
|
+
id?: ID
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* This is primarily used in websocket connections so we don't need to pass the auth token for each weboscket message
|
|
23
|
+
*/
|
|
24
|
+
expiresAt = new Date()
|
|
25
|
+
|
|
26
|
+
private savedUser?: User
|
|
27
|
+
|
|
28
|
+
get authenticable(): typeof User {
|
|
29
|
+
return this.authConfig.entity
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// @BeforeRequest() // TODO implement the container's '@BeforeRequest' decorator to do something before each request.
|
|
33
|
+
// For example, load the authenticated user in memory again
|
|
34
|
+
// async before() {
|
|
35
|
+
// if (!this.id) {
|
|
36
|
+
// return undefined
|
|
37
|
+
// throw new Error('You tried to use the Auth service without authenticating the user. ' +
|
|
38
|
+
// 'Please use the AuthMiddleware or authenticate the user before using this route')
|
|
39
|
+
// }
|
|
40
|
+
//
|
|
41
|
+
// const user = await this.authenticable.find(this.id)
|
|
42
|
+
// if (!user) {
|
|
43
|
+
// throw new Error(`Authenticated user with ID ${this.id} not found `)
|
|
44
|
+
// }
|
|
45
|
+
// this.savedUser = await this.authenticable.find(this.id)
|
|
46
|
+
// }
|
|
47
|
+
|
|
48
|
+
async user<T extends User>(): Promise<T> {
|
|
49
|
+
// if (this.savedUser) { // TODO this if statement should be removed using the @BeforeRequest decorator above
|
|
50
|
+
// return this.savedUser as T
|
|
51
|
+
// }
|
|
52
|
+
|
|
53
|
+
if (!this.id) {
|
|
54
|
+
throw new Error('You tried to use the Auth service without authenticating the user. Please use the AuthMiddleware or authenticate the user before using this route')
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const user = await this.authenticable.find(this.id)
|
|
58
|
+
|
|
59
|
+
if (!user) {
|
|
60
|
+
throw new Error(`Could not find a user with id '${this.id}'`)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return this.savedUser = user as T
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async login(username: string, password: string): Promise<string> {
|
|
67
|
+
const user = await this.authenticable.where((new this.authenticable).getUsername() as EntityKeys<User>, username).first()
|
|
68
|
+
if (!user || !await this.crypt.compare(password, user.password)) {
|
|
69
|
+
throw new Error('Invalid credentials')
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
delete this.savedUser
|
|
73
|
+
return this.sign(this.id = user.id)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
loginById(id: ID): Promise<string> {
|
|
77
|
+
return this.sign(id)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
loginUser(authenticable: User): Promise<string> {
|
|
81
|
+
return this.loginById(authenticable[authenticable.getId()])
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
sign(id: ID, expiresAtTimestampInSeconds?: number): Promise<string> {
|
|
85
|
+
return this.jwt.sign(
|
|
86
|
+
{
|
|
87
|
+
sub: this.set(id, expiresAtTimestampInSeconds)
|
|
88
|
+
},
|
|
89
|
+
this.authConfig.signature,
|
|
90
|
+
{
|
|
91
|
+
expiresIn: this.authConfig.duration
|
|
92
|
+
}
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async register<T extends User>(username: string, password: string): Promise<T> {
|
|
97
|
+
return await this.authenticable.create({
|
|
98
|
+
[(new this.authenticable).getUsername() as EntityKeys<T>]: username,
|
|
99
|
+
password: await this.crypt.hash(password, this.authConfig.saltRounds),
|
|
100
|
+
}) as T
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async verify(token: string): Promise<JWToken<number>> {
|
|
104
|
+
const payload = await this.jwt.verify<number>(token, this.authConfig.signature)
|
|
105
|
+
|
|
106
|
+
this.set(payload.sub, payload.exp)
|
|
107
|
+
|
|
108
|
+
return payload
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private set(id: ID, expiresAtTimestampInSeconds?: number): ID {
|
|
112
|
+
if (expiresAtTimestampInSeconds) {
|
|
113
|
+
this.expiresAt = new Date(expiresAtTimestampInSeconds * 1000)
|
|
114
|
+
} else {
|
|
115
|
+
this.expiresAt = new Date()
|
|
116
|
+
this.expiresAt.setSeconds(this.expiresAt.getSeconds() + this.authConfig.duration)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return this.id = id
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Column, Entity, EntityColumns, ID, PrimaryColumn } from '../../Database'
|
|
2
|
+
import { Authenticatable } from './Authenticatable'
|
|
3
|
+
|
|
4
|
+
export class User extends Entity implements Authenticatable<User> {
|
|
5
|
+
|
|
6
|
+
@PrimaryColumn()
|
|
7
|
+
id: ID
|
|
8
|
+
|
|
9
|
+
@Column()
|
|
10
|
+
email: string
|
|
11
|
+
|
|
12
|
+
@Column()
|
|
13
|
+
password: string
|
|
14
|
+
|
|
15
|
+
getId(): EntityColumns<User> {
|
|
16
|
+
return 'id'
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
getPassword(): EntityColumns<User> {
|
|
20
|
+
return 'password'
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
getUsername(): EntityColumns<User> {
|
|
24
|
+
return 'email'
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
}
|